diff --git a/.gitignore b/.gitignore index ed647d4c..43674a41 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ javadoc *.java~ build .gradle +/.settings/ +/.classpath diff --git a/build.gradle b/build.gradle index d36e0805..b5f81c94 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,8 @@ archivesBaseName = 'languagesupport' dependencies { compile group: 'org.mozilla', name: 'rhino', version: '1.7.6' + compile 'com.fifesoft:autocomplete:2.5.8' + // compile 'org.javassist:javassist:3.20.0-GA' testCompile group: 'junit', name: 'junit', version: '4.11' } diff --git a/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java b/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java index 6f948f4c..b770924b 100644 --- a/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java +++ b/src/main/java/org/fife/rsta/ac/LanguageSupportFactory.java @@ -66,7 +66,7 @@ private LanguageSupportFactory() { * * @param style The language to add support for. This should be one of * the values defined in {@link SyntaxConstants}. Any previous - * language support for this language is removed. + * language support for this language is removed. * @param lsClassName The class name of the LanguageSupport. */ public void addLanguageSupport(String style, String lsClassName) { @@ -104,8 +104,8 @@ private void createSupportMap() { prefix + "perl.PerlLanguageSupport"); addLanguageSupport(SyntaxConstants.SYNTAX_STYLE_PHP, prefix + "php.PhpLanguageSupport"); - addLanguageSupport(SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT, - prefix + "ts.TypeScriptLanguageSupport"); +// addLanguageSupport(SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT, +// prefix + "ts.TypeScriptLanguageSupport"); addLanguageSupport(SyntaxConstants.SYNTAX_STYLE_UNIX_SHELL, prefix + "sh.ShellLanguageSupport"); addLanguageSupport(SyntaxConstants.SYNTAX_STYLE_XML, diff --git a/src/main/java/org/fife/rsta/ac/demo/AboutDialog.java b/src/main/java/org/fife/rsta/ac/demo/AboutDialog.java index ecec2ee6..9faee6d0 100644 --- a/src/main/java/org/fife/rsta/ac/demo/AboutDialog.java +++ b/src/main/java/org/fife/rsta/ac/demo/AboutDialog.java @@ -21,6 +21,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; + import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; @@ -38,12 +39,10 @@ import org.fife.rsta.ac.java.buildpath.JarLibraryInfo; import org.fife.rsta.ac.java.buildpath.LibraryInfo; -import org.fife.rsta.ac.perl.PerlLanguageSupport; - /** * The "About" dialog for the demo application. - * + * * @author Robert Futrell * @version 1.0 */ @@ -51,13 +50,12 @@ public class AboutDialog extends JDialog { private final Border empty5Border = BorderFactory.createEmptyBorder(5, 5, 5, 5); - public AboutDialog(DemoApp parent) { super(parent); JPanel cp = new JPanel(new BorderLayout()); -// cp.setBorder(empty5Border); + // cp.setBorder(empty5Border); Box box = Box.createVerticalBox(); @@ -80,12 +78,10 @@ public AboutDialog(DemoApp parent) { JTextArea textArea = new JTextArea(6, 60); // Windows LAF picks a bad font for text areas, for some reason. textArea.setFont(labelFont); - textArea.setText("Version 0.2\n\n" + - "Demonstrates basic features of the RSTALanguageSupport library.\n" + - "Note that some features for some languages may not work unless your system " + - "is set up properly.\nFor example, Java code completion requries a JRE on " + - "your PATH, and Perl completion requires the Perl executable to be on your " + - "PATH."); + textArea.setText("Version 0.2\n\n" + "Demonstrates basic features of the RSTALanguageSupport library.\n" + + "Note that some features for some languages may not work unless your system " + + "is set up properly.\nFor example, Java code completion requries a JRE on " + + "your PATH, and Perl completion requires the Perl executable to be on your " + "PATH."); textArea.setEditable(false); textArea.setBackground(Color.white); textArea.setLineWrap(true); @@ -99,27 +95,30 @@ public AboutDialog(DemoApp parent) { SpringLayout sl = new SpringLayout(); JPanel temp = new JPanel(sl); JLabel perlLabel = new JLabel("Perl install location:"); - File loc = PerlLanguageSupport.getDefaultPerlInstallLocation(); - String text = loc==null ? null : loc.getAbsolutePath(); + // File loc = PerlLanguageSupport.getDefaultPerlInstallLocation(); + String text = null; JTextField perlField = createTextField(text); JLabel javaLabel = new JLabel("Java home:"); String jre = null; LibraryInfo info = LibraryInfo.getMainJreJarInfo(); - if (info!=null) { // Should always be true - File jarFile = ((JarLibraryInfo)info).getJarFile(); + if (info != null) { // Should always be true + File jarFile = ((JarLibraryInfo) info).getJarFile(); jre = jarFile.getParentFile().getParentFile().getAbsolutePath(); } JTextField javaField = createTextField(jre); if (getComponentOrientation().isLeftToRight()) { - temp.add(perlLabel); temp.add(perlField); - temp.add(javaLabel); temp.add(javaField); - } - else { - temp.add(perlField); temp.add(perlLabel); - temp.add(javaField); temp.add(javaLabel); + temp.add(perlLabel); + temp.add(perlField); + temp.add(javaLabel); + temp.add(javaField); + } else { + temp.add(perlField); + temp.add(perlLabel); + temp.add(javaField); + temp.add(javaLabel); } - makeSpringCompactGrid(temp, 2, 2, 5,5, 15,5); + makeSpringCompactGrid(temp, 2, 2, 5, 5, 15, 5); box.add(temp); box.add(Box.createVerticalGlue()); @@ -146,7 +145,6 @@ public void actionPerformed(ActionEvent e) { } - private JPanel addLeftAligned(Component toAdd, Container addTo) { JPanel temp = new JPanel(new BorderLayout()); temp.setOpaque(false); // For ones on white background. @@ -155,7 +153,6 @@ private JPanel addLeftAligned(Component toAdd, Container addTo) { return temp; } - private JTextField createTextField(String text) { JTextField field = new JTextField(text); field.setEditable(false); @@ -164,107 +161,102 @@ private JTextField createTextField(String text) { return field; } - /** - * Used by makeSpringCompactGrid. This is ripped off directly from + * Used by makeSpringCompactGrid. This is ripped off directly from * SpringUtilities.java in the Sun Java Tutorial. * - * @param parent The container whose layout must be an instance of - * SpringLayout. - * @return The spring constraints for the specified component contained - * in parent. + * @param parent + * The container whose layout must be an instance of + * SpringLayout. + * @return The spring constraints for the specified component contained in + * parent. */ - private static final SpringLayout.Constraints getConstraintsForCell( - int row, int col, - Container parent, int cols) { + private static final SpringLayout.Constraints getConstraintsForCell(int row, int col, Container parent, int cols) { SpringLayout layout = (SpringLayout) parent.getLayout(); Component c = parent.getComponent(row * cols + col); return layout.getConstraints(c); } - /** - * This method is ripped off from SpringUtilities.java found - * on Sun's Java Tutorial pages. It takes a component whose layout is - * SpringLayout and organizes the components it contains into - * a nice grid. - * Aligns the first rows * cols components of - * parent in a grid. Each component in a column is as wide as - * the maximum preferred width of the components in that column; height is - * similarly determined for each row. The parent is made just big enough - * to fit them all. + * This method is ripped off from SpringUtilities.java found on + * Sun's Java Tutorial pages. It takes a component whose layout is + * SpringLayout and organizes the components it contains into a + * nice grid. Aligns the first rows * cols + * components of parent in a grid. Each component in a column + * is as wide as the maximum preferred width of the components in that + * column; height is similarly determined for each row. The parent is made + * just big enough to fit them all. * - * @param parent The container whose layout is SpringLayout. - * @param rows The number of rows of components to make in the container. - * @param cols The umber of columns of components to make. - * @param initialX The x-location to start the grid at. - * @param initialY The y-location to start the grid at. - * @param xPad The x-padding between cells. - * @param yPad The y-padding between cells. + * @param parent + * The container whose layout is SpringLayout. + * @param rows + * The number of rows of components to make in the container. + * @param cols + * The umber of columns of components to make. + * @param initialX + * The x-location to start the grid at. + * @param initialY + * The y-location to start the grid at. + * @param xPad + * The x-padding between cells. + * @param yPad + * The y-padding between cells. */ - public static final void makeSpringCompactGrid(Container parent, int rows, - int cols, int initialX, int initialY, - int xPad, int yPad) { + public static final void makeSpringCompactGrid(Container parent, int rows, int cols, int initialX, int initialY, + int xPad, int yPad) { SpringLayout layout; try { - layout = (SpringLayout)parent.getLayout(); + layout = (SpringLayout) parent.getLayout(); } catch (ClassCastException cce) { - System.err.println("The first argument to makeCompactGrid " + - "must use SpringLayout."); + System.err.println("The first argument to makeCompactGrid " + "must use SpringLayout."); return; } - //Align all cells in each column and make them the same width. + // Align all cells in each column and make them the same width. Spring x = Spring.constant(initialX); for (int c = 0; c < cols; c++) { Spring width = Spring.constant(0); for (int r = 0; r < rows; r++) { - width = Spring.max(width, - getConstraintsForCell( - r, c, parent, cols).getWidth()); + width = Spring.max(width, getConstraintsForCell(r, c, parent, cols).getWidth()); } for (int r = 0; r < rows; r++) { - SpringLayout.Constraints constraints = - getConstraintsForCell(r, c, parent, cols); + SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, cols); constraints.setX(x); constraints.setWidth(width); } x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad))); } - //Align all cells in each row and make them the same height. + // Align all cells in each row and make them the same height. Spring y = Spring.constant(initialY); for (int r = 0; r < rows; r++) { Spring height = Spring.constant(0); for (int c = 0; c < cols; c++) { - height = Spring.max(height, - getConstraintsForCell(r, c, parent, cols).getHeight()); + height = Spring.max(height, getConstraintsForCell(r, c, parent, cols).getHeight()); } for (int c = 0; c < cols; c++) { - SpringLayout.Constraints constraints = - getConstraintsForCell(r, c, parent, cols); + SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, cols); constraints.setY(y); constraints.setHeight(height); } y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad))); } - //Set the parent's size. + // Set the parent's size. SpringLayout.Constraints pCons = layout.getConstraints(parent); pCons.setConstraint(SpringLayout.SOUTH, y); pCons.setConstraint(SpringLayout.EAST, x); } - /** * The border of the "top section" of the About dialog. */ private static class TopBorder extends AbstractBorder { @Override - public Insets getBorderInsets(Component c) { + public Insets getBorderInsets(Component c) { return getBorderInsets(c, new Insets(0, 0, 0, 0)); } @@ -276,17 +268,15 @@ public Insets getBorderInsets(Component c, Insets insets) { } @Override - public void paintBorder(Component c, Graphics g, int x, int y, - int width, int height) { + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { Color color = UIManager.getColor("controlShadow"); - if (color==null) { + if (color == null) { color = SystemColor.controlShadow; } g.setColor(color); - g.drawLine(x,y+height-1, x+width,y+height-1); + g.drawLine(x, y + height - 1, x + width, y + height - 1); } } - } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/demo/DemoRootPane.java b/src/main/java/org/fife/rsta/ac/demo/DemoRootPane.java index ecf75cfc..f9f2a004 100644 --- a/src/main/java/org/fife/rsta/ac/demo/DemoRootPane.java +++ b/src/main/java/org/fife/rsta/ac/demo/DemoRootPane.java @@ -18,9 +18,25 @@ import java.io.InputStreamReader; import java.net.URL; -import javax.swing.*; +import javax.swing.Action; +import javax.swing.ButtonGroup; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JRootPane; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTree; +import javax.swing.SwingUtilities; +import javax.swing.ToolTipManager; +import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; -import javax.swing.event.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; import javax.swing.tree.TreeNode; import org.fife.rsta.ac.AbstractSourceTree; @@ -28,17 +44,15 @@ import org.fife.rsta.ac.LanguageSupportFactory; import org.fife.rsta.ac.java.JavaLanguageSupport; import org.fife.rsta.ac.java.tree.JavaOutlineTree; -import org.fife.rsta.ac.js.tree.JavaScriptOutlineTree; -import org.fife.rsta.ac.xml.tree.XmlOutlineTree; import org.fife.ui.rsyntaxtextarea.ErrorStrip; -import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rtextarea.RTextScrollPane; /** * The root pane used by the demos. This allows both the applet and the - * stand-alone application to share the same UI. + * stand-alone application to share the same UI. * * @author Robert Futrell * @version 1.0 @@ -65,13 +79,13 @@ public DemoRootPane() { } catch (IOException ioe) { ioe.printStackTrace(); } - + // Dummy tree keeps JViewport's "background" looking right initially JTree dummy = new JTree((TreeNode)null); treeSP = new JScrollPane(dummy); textArea = createTextArea(); - setText("CExample.txt", SYNTAX_STYLE_C); + setText("JavaExample.txt", SYNTAX_STYLE_JAVA); scrollPane = new RTextScrollPane(textArea, true); scrollPane.setIconRowHeaderEnabled(true); scrollPane.getGutter().setBookmarkingEnabled(true); @@ -127,7 +141,7 @@ private JMenuBar createMenuBar() { addItem(new StyleAction(this, "HTML", "HtmlExample.txt", SYNTAX_STYLE_HTML), bg, menu); addItem(new StyleAction(this, "PHP", "PhpExample.txt", SYNTAX_STYLE_PHP), bg, menu); addItem(new StyleAction(this, "sh", "ShellExample.txt", SYNTAX_STYLE_UNIX_SHELL), bg, menu); - addItem(new StyleAction(this, "TypeScript", "TypeScriptExample.txt", SYNTAX_STYLE_TYPESCRIPT), bg, menu); +// addItem(new StyleAction(this, "TypeScript", "TypeScriptExample.txt", SYNTAX_STYLE_TYPESCRIPT), bg, menu); addItem(new StyleAction(this, "XML", "XMLExample.txt", SYNTAX_STYLE_XML), bg, menu); menu.getItem(0).setSelected(true); mb.add(menu); @@ -143,7 +157,7 @@ private JMenuBar createMenuBar() { menu = new JMenu("View"); menu.add(new JCheckBoxMenuItem(new ToggleLayeredHighlightsAction(this))); mb.add(menu); - + menu = new JMenu("Help"); menu.add(new JMenuItem(new AboutAction(this))); mb.add(menu); @@ -242,12 +256,12 @@ private void refreshSourceTree() { if (SyntaxConstants.SYNTAX_STYLE_JAVA.equals(language)) { tree = new JavaOutlineTree(); } - else if (SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT.equals(language)) { - tree = new JavaScriptOutlineTree(); - } - else if (SyntaxConstants.SYNTAX_STYLE_XML.equals(language)) { - tree = new XmlOutlineTree(); - } +// else if (SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT.equals(language)) { +// tree = new JavaScriptOutlineTree(); +// } +// else if (SyntaxConstants.SYNTAX_STYLE_XML.equals(language)) { +// tree = new XmlOutlineTree(); +// } else { tree = null; } diff --git a/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java b/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java index 79cbd144..dc822291 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaLanguageSupport.java @@ -1,618 +1,669 @@ -/* - * 03/21/2010 - * - * Copyright (C) 2010 Robert Futrell - * robert_futrell at users.sourceforge.net - * http://fifesoft.com/rsyntaxtextarea - * - * This library is distributed under a modified BSD license. See the included - * RSTALanguageSupport.License.txt file for details. - */ -package org.fife.rsta.ac.java; - -import java.awt.Point; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import javax.swing.ActionMap; -import javax.swing.InputMap; -import javax.swing.KeyStroke; -import javax.swing.Timer; -import javax.swing.event.CaretEvent; -import javax.swing.event.CaretListener; -import javax.swing.text.BadLocationException; -import javax.swing.text.Document; -import javax.swing.text.Element; - -import org.fife.rsta.ac.AbstractLanguageSupport; -import org.fife.rsta.ac.GoToMemberAction; -import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; -import org.fife.rsta.ac.java.rjc.ast.Package; -import org.fife.rsta.ac.java.tree.JavaOutlineTree; -import org.fife.ui.autocomplete.AutoCompletion; -import org.fife.ui.autocomplete.Completion; -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; - - -/** - * Language support for Java. - * - * @author Robert Futrell - * @version 1.0 - * @see JavaOutlineTree - */ -public class JavaLanguageSupport extends AbstractLanguageSupport { - - /** - * Maps JavaParsers to Info instances about them. - */ - private Map parserToInfoMap; - - /** - * The shared jar manager to use with all {@link JavaCompletionProvider}s, - * or null if each one should have a unique jar manager. - */ - private JarManager jarManager; - - /** - * Client property installed on text areas that points to a listener. - */ - private static final String PROPERTY_LISTENER = - "org.fife.rsta.ac.java.JavaLanguageSupport.Listener"; - - - /** - * Constructor. - */ - public JavaLanguageSupport() { - parserToInfoMap = new HashMap(); - jarManager = new JarManager(); - setAutoActivationEnabled(true); - setParameterAssistanceEnabled(true); - setShowDescWindow(true); - } - - - /** - * Returns the completion provider running on a text area with this Java - * language support installed. - * - * @param textArea The text area. - * @return The completion provider. This will be null if - * the text area does not have this JavaLanguageSupport - * installed. - */ - public JavaCompletionProvider getCompletionProvider( - RSyntaxTextArea textArea) { - AutoCompletion ac = getAutoCompletionFor(textArea); - return (JavaCompletionProvider)ac.getCompletionProvider(); - } - - - /** - * Returns the shared jar manager instance. - * NOTE: This method will be removed over time, as the Java support becomes - * more robust! - * - * @return The shared jar manager. - */ - public JarManager getJarManager() { - return jarManager; - } - - - /** - * Returns the Java parser running on a text area with this Java language - * support installed. - * - * @param textArea The text area. - * @return The Java parser. This will be null if the text - * area does not have this JavaLanguageSupport installed. - */ - public JavaParser getParser(RSyntaxTextArea textArea) { - // Could be a parser for another language. - Object parser = textArea.getClientProperty(PROPERTY_LANGUAGE_PARSER); - if (parser instanceof JavaParser) { - return (JavaParser)parser; - } - return null; - } - - - /** - * {@inheritDoc} - */ - public void install(RSyntaxTextArea textArea) { - - JavaCompletionProvider p = new JavaCompletionProvider(jarManager); - // Can't use createAutoCompletion(), as Java's is "special." - AutoCompletion ac = new JavaAutoCompletion(p, textArea); - ac.setListCellRenderer(new JavaCellRenderer()); - ac.setAutoCompleteEnabled(isAutoCompleteEnabled()); - ac.setAutoActivationEnabled(isAutoActivationEnabled()); - ac.setAutoActivationDelay(getAutoActivationDelay()); - ac.setExternalURLHandler(new JavadocUrlHandler()); - ac.setParameterAssistanceEnabled(isParameterAssistanceEnabled()); - ac.setParamChoicesRenderer(new JavaParamListCellRenderer()); - ac.setShowDescWindow(getShowDescWindow()); - ac.install(textArea); - installImpl(textArea, ac); - - textArea.setToolTipSupplier(p); - - Listener listener = new Listener(textArea); - textArea.putClientProperty(PROPERTY_LISTENER, listener); - - JavaParser parser = new JavaParser(textArea); - textArea.putClientProperty(PROPERTY_LANGUAGE_PARSER, parser); - textArea.addParser(parser); - textArea.setToolTipSupplier(p); - - Info info = new Info(textArea, p, parser); - parserToInfoMap.put(parser, info); - - installKeyboardShortcuts(textArea); - - textArea.setLinkGenerator(new JavaLinkGenerator(this)); - - } - - - /** - * Installs extra keyboard shortcuts supported by this language support. - * - * @param textArea The text area to install the shortcuts into. - */ - private void installKeyboardShortcuts(RSyntaxTextArea textArea) { - - InputMap im = textArea.getInputMap(); - ActionMap am = textArea.getActionMap(); - int c = textArea.getToolkit().getMenuShortcutKeyMask(); - int shift = InputEvent.SHIFT_MASK; - - im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, c|shift), "GoToType"); - am.put("GoToType", new GoToMemberAction(JavaOutlineTree.class)); - - } - - - /** - * {@inheritDoc} - */ - public void uninstall(RSyntaxTextArea textArea) { - - uninstallImpl(textArea); - - JavaParser parser = getParser(textArea); - Info info = parserToInfoMap.remove(parser); - if (info!=null) { // Should always be true - parser.removePropertyChangeListener( - JavaParser.PROPERTY_COMPILATION_UNIT, info); - } - textArea.removeParser(parser); - textArea.putClientProperty(PROPERTY_LANGUAGE_PARSER, null); - textArea.setToolTipSupplier(null); - - Object listener = textArea.getClientProperty(PROPERTY_LISTENER); - if (listener instanceof Listener) { // Should always be true - ((Listener)listener).uninstall(); - textArea.putClientProperty(PROPERTY_LISTENER, null); - } - - uninstallKeyboardShortcuts(textArea); - textArea.setLinkGenerator(null); - - } - - - /** - * Uninstalls any keyboard shortcuts specific to this language support. - * - * @param textArea The text area to uninstall the actions from. - */ - private void uninstallKeyboardShortcuts(RSyntaxTextArea textArea) { - - InputMap im = textArea.getInputMap(); - ActionMap am = textArea.getActionMap(); - int c = textArea.getToolkit().getMenuShortcutKeyMask(); - int shift = InputEvent.SHIFT_MASK; - - im.remove(KeyStroke.getKeyStroke(KeyEvent.VK_O, c|shift)); - am.remove("GoToType"); - - } - - - /** - * Information about an import statement to add and where it should be - * added. This is used internally when a class completion is inserted, - * and it needs an import statement added to the source. - */ - private static class ImportToAddInfo { - - public int offs; - public String text; - - public ImportToAddInfo(int offset, String text) { - this.offs = offset; - this.text = text; - } - - } - - - /** - * Manages information about the parsing/auto-completion for a single text - * area. Unlike many simpler language supports, - * JavaLanguageSupport cannot share any information amongst - * instances of RSyntaxTextArea. - */ - private static class Info implements PropertyChangeListener { - - public JavaCompletionProvider provider; - //public JavaParser parser; - - public Info(RSyntaxTextArea textArea, JavaCompletionProvider provider, - JavaParser parser) { - this.provider = provider; - //this.parser = parser; - parser.addPropertyChangeListener( - JavaParser.PROPERTY_COMPILATION_UNIT, this); - } - - /** - * Called when a text area is re-parsed. - * - * @param e The event. - */ - public void propertyChange(PropertyChangeEvent e) { - - String name = e.getPropertyName(); - - if (JavaParser.PROPERTY_COMPILATION_UNIT.equals(name)) { - CompilationUnit cu = (CompilationUnit)e.getNewValue(); -// structureTree.update(file, cu); -// updateTable(); - provider.setCompilationUnit(cu); - } - - } - - } - - - /** - * A hack of AutoCompletion that forces the JavaParser - * to re-parse the document when the user presses ctrl+space. - */ - private class JavaAutoCompletion extends AutoCompletion { - - private RSyntaxTextArea textArea; - private String replacementTextPrefix; - - public JavaAutoCompletion(JavaCompletionProvider provider, - RSyntaxTextArea textArea) { - super(provider); - this.textArea = textArea; - } - - private String getCurrentLineText() { - - int caretPosition = textArea.getCaretPosition(); - Element root = textArea.getDocument().getDefaultRootElement(); - int line= root.getElementIndex(caretPosition); - Element elem = root.getElement(line); - int endOffset = elem.getEndOffset(); - int lineStart = elem.getStartOffset(); - - String text = ""; - try { - text = textArea.getText(lineStart, endOffset-lineStart).trim(); - } catch (BadLocationException e) { - e.printStackTrace(); - } - - return text; - - } - - /** - * Overridden to allow for prepending to the replacement text. This - * allows us to insert fully qualified class names. instead of - * unqualified ones, if necessary (i.e. if the user tries to - * auto-complete javax.swing.text.Document, but they've - * explicitly imported org.w3c.dom.Document - we need to - * insert the fully qualified name in that case). - */ - @Override - protected String getReplacementText(Completion c, Document doc, - int start, int len) { - String text = super.getReplacementText(c, doc, start, len); - if (replacementTextPrefix!=null) { - text = replacementTextPrefix + text; - replacementTextPrefix = null; - } - return text; - } - - /** - * Determines whether the class name being completed has been imported, - * and if it hasn't, returns the import statement that should be added - * for it. Alternatively, if the class hasn't been imported, but a - * class with the same (unqualified) name HAS been imported, this - * method sets things up so the fully-qualified version of this class's - * name is inserted.

- * - * Thanks to Guilherme Joao Frantz and Jonatas Schuler for helping - * with the patch! - * - * @param c The completion being inserted. - * @return Whether an import was added. - */ - private ImportToAddInfo getShouldAddImport(ClassCompletion cc) { - - String text = getCurrentLineText(); - - // Make sure we're not currently typing an import statement. - if (!text.startsWith("import ")) { - - JavaCompletionProvider provider = (JavaCompletionProvider) - getCompletionProvider(); - CompilationUnit cu = provider.getCompilationUnit(); - int offset = 0; - boolean alreadyImported = false; - - // Try to bail early, if possible. - if (cu==null) { // Can never happen, right? - return null; - } - if ("java.lang".equals(cc.getPackageName())) { - // Package java.lang is "imported" by default. - return null; - } - - String className = cc.getClassName(false); - String fqClassName = cc.getClassName(true); - - // If the completion is in the same package as the source we're - // editing (or both are in the default package), bail. - int lastClassNameDot = fqClassName.lastIndexOf('.'); - boolean ccInPackage = lastClassNameDot>-1; - Package pkg = cu.getPackage(); - if (ccInPackage && pkg!=null) { - String ccPkg = fqClassName.substring(0, lastClassNameDot); - String pkgName = pkg.getName(); - if (ccPkg.equals(pkgName)) { - return null; - } - } - else if (!ccInPackage && pkg==null) { - return null; - } - - // Loop through all import statements. - Iterator i = cu.getImportIterator(); - for (; i.hasNext(); ) { - - ImportDeclaration id = i.next(); - offset = id.getNameEndOffset() + 1; - - // Pulling in static methods, etc. from a class - skip - if (id.isStatic()) { - continue; - } - - // Importing all classes in the package... - else if (id.isWildcard()) { - // NOTE: Class may be in default package... - if (lastClassNameDot>-1) { - String imported = id.getName(); - int dot = imported.lastIndexOf('.'); - String importedPkg = imported.substring(0, dot); - String classPkg = fqClassName.substring(0, lastClassNameDot); - if (importedPkg.equals(classPkg)) { - alreadyImported = true; - break; - } - } - } - - // Importing a single class from a package... - else { - - String fullyImportedClassName = id.getName(); - int dot = fullyImportedClassName.lastIndexOf('.'); - String importedClassName = fullyImportedClassName. - substring(dot + 1); - - // If they explicitly imported a class with the - // same name, but it's in a different package, then - // the user is required to fully-qualify the class - // in their code (if unqualified, it would be - // assumed to be of the type of the qualified - // class). - if (className.equals(importedClassName)) { - offset = -1; // Means "must fully qualify" - if (fqClassName.equals(fullyImportedClassName)) { - alreadyImported = true; - } - break; - } - - } - - } - - // If the class wasn't imported, we'll need to add an - // import statement! - if (!alreadyImported) { - - StringBuilder importToAdd = new StringBuilder(); - - // If there are no previous imports, add the import - // statement after the package line (if any). - if (offset == 0) { - if (pkg!=null) { - offset = pkg.getNameEndOffset() + 1; - // Keep an empty line between package and imports. - importToAdd.append('\n'); - } - } - - // We read through all imports, but didn't find our class. - // Add a new import statement after the last one. - if (offset > -1) { - //System.out.println(classCompletion.getAlreadyEntered(textArea)); - if (offset>0) { - importToAdd.append("\nimport ").append(fqClassName).append(';'); - } - else { - importToAdd.append("import ").append(fqClassName).append(";\n"); - } - // TODO: Determine whether the imports are alphabetical, - // and if so, add the new one alphabetically. - return new ImportToAddInfo(offset, importToAdd.toString()); - } - - // Otherwise, either the class was imported, or a class - // with the same name was explicitly imported. - else { - // Another class with the same name was imported. - // We must insert the fully-qualified class name - // so the compiler resolves the correct class. - int dot = fqClassName.lastIndexOf('.'); - if (dot>-1) { - String pkgName = fqClassName.substring(0, dot+1); - replacementTextPrefix = pkgName; - } - } - - } - - } - - return null; - - } - - /** - * Overridden to handle special cases, because sometimes Java code - * completions will edit more in the source file than just the text - * at the current caret position. - */ - @Override - protected void insertCompletion(Completion c, - boolean typedParamListStartChar) { - - ImportToAddInfo importInfo = null; - - // We special-case class completions because they may add import - // statements to the top of our source file. We don't add the - // (possible) new import statement until after the completion is - // inserted; that way, when we treat it as an atomic undo/redo, - // when the user undoes the completion, the caret stays in the - // code instead of jumping to the import. - if (c instanceof ClassCompletion) { - importInfo = getShouldAddImport((ClassCompletion)c); - if (importInfo!=null) { - textArea.beginAtomicEdit(); - } - } - - try { - super.insertCompletion(c, typedParamListStartChar); - if (importInfo!=null) { - textArea.insert(importInfo.text, importInfo.offs); - } - } finally { - // Be safe and always pair beginAtomicEdit() and endAtomicEdit() - textArea.endAtomicEdit(); - } - - } - - @Override - protected int refreshPopupWindow() { - // Force the parser to re-parse - JavaParser parser = getParser(textArea); - RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); - String style = textArea.getSyntaxEditingStyle(); - parser.parse(doc, style); - return super.refreshPopupWindow(); - } - - } - - - /** - * Listens for various events in a text area editing Java (in particular, - * caret events, so we can track the "active" code block). - */ - private class Listener implements CaretListener, ActionListener { - - private RSyntaxTextArea textArea; - private Timer t; - - public Listener(RSyntaxTextArea textArea) { - this.textArea = textArea; - textArea.addCaretListener(this); - t = new Timer(650, this); - t.setRepeats(false); - } - - public void actionPerformed(ActionEvent e) { - - JavaParser parser = getParser(textArea); - if (parser==null) { - return; // Shouldn't happen - } - CompilationUnit cu = parser.getCompilationUnit(); - - // Highlight the line range of the Java method being edited in the - // gutter. - if (cu != null) { // Should always be true - int dot = textArea.getCaretPosition(); - Point p = cu.getEnclosingMethodRange(dot); - if (p != null) { - try { - int startLine = textArea.getLineOfOffset(p.x); - // Unterminated blocks can end in Integer.MAX_VALUE - int endOffs = Math.min(p.y, - textArea.getDocument().getLength()); - int endLine = textArea.getLineOfOffset(endOffs); - textArea.setActiveLineRange(startLine, endLine); - } catch (BadLocationException ble) { - ble.printStackTrace(); - } - } - else { - textArea.setActiveLineRange(-1, -1); - } - } - - } - - public void caretUpdate(CaretEvent e) { - t.restart(); - } - - /** - * Should be called whenever Java language support is removed from a - * text area. - */ - public void uninstall() { - textArea.removeCaretListener(this); - } - - } - - +/* + * 03/21/2010 + * + * Copyright (C) 2010 Robert Futrell + * robert_futrell at users.sourceforge.net + * http://fifesoft.com/rsyntaxtextarea + * + * This library is distributed under a modified BSD license. See the included + * RSTALanguageSupport.License.txt file for details. + */ +package org.fife.rsta.ac.java; + +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.swing.Action; +import javax.swing.ActionMap; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.KeyStroke; +import javax.swing.Timer; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; + +import org.apache.log4j.Logger; +import org.fife.rsta.ac.AbstractLanguageSupport; +import org.fife.rsta.ac.GoToMemberAction; +import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; +import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; +import org.fife.rsta.ac.java.rjc.ast.Package; +import org.fife.rsta.ac.java.tree.JavaOutlineTree; +import org.fife.ui.autocomplete.AutoCompletion; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; + +import sun.reflect.Reflection; + + +/** + * Language support for Java. + * + * @author Robert Futrell + * @version 1.0 + * @see JavaOutlineTree + */ +public class JavaLanguageSupport extends AbstractLanguageSupport { + + private static final Logger log = Logger.getLogger(Reflection.getCallerClass(1)); + + + /** + * Maps JavaParsers to Info instances about them. + */ + private Map parserToInfoMap; + + /** + * The shared jar manager to use with all {@link JavaCompletionProvider}s, + * or null if each one should have a unique jar manager. + */ + private JarManager jarManager; + + /** + * Client property installed on text areas that points to a listener. + */ + private static final String PROPERTY_LISTENER = + "org.fife.rsta.ac.java.JavaLanguageSupport.Listener"; + + + /** + * Constructor. + */ + public JavaLanguageSupport() { + parserToInfoMap = new HashMap(); + jarManager = new JarManager(); + setAutoActivationEnabled(true); + setParameterAssistanceEnabled(true); + setShowDescWindow(true); + } + + + /** + * Returns the completion provider running on a text area with this Java + * language support installed. + * + * @param textArea The text area. + * @return The completion provider. This will be null if + * the text area does not have this JavaLanguageSupport + * installed. + */ + public JavaCompletionProvider getCompletionProvider( + RSyntaxTextArea textArea) { + AutoCompletion ac = getAutoCompletionFor(textArea); + return (JavaCompletionProvider)ac.getCompletionProvider(); + } + + + /** + * Returns the shared jar manager instance. + * NOTE: This method will be removed over time, as the Java support becomes + * more robust! + * + * @return The shared jar manager. + */ + public JarManager getJarManager() { + return jarManager; + } + + + /** + * Returns the Java parser running on a text area with this Java language + * support installed. + * + * @param textArea The text area. + * @return The Java parser. This will be null if the text + * area does not have this JavaLanguageSupport installed. + */ + public JavaParser getParser(RSyntaxTextArea textArea) { + // Could be a parser for another language. + Object parser = textArea.getClientProperty(PROPERTY_LANGUAGE_PARSER); + if (parser instanceof JavaParser) { + return (JavaParser)parser; + } + return null; + } + + + /** + * {@inheritDoc} + */ + public void install(RSyntaxTextArea textArea) { + + JavaCompletionProvider p = new JavaCompletionProvider(jarManager); + // Can't use createAutoCompletion(), as Java's is "special." + AutoCompletion ac = new JavaAutoCompletion(p, textArea); + ac.setListCellRenderer(new JavaCellRenderer()); + ac.setAutoCompleteEnabled(isAutoCompleteEnabled()); + ac.setAutoActivationEnabled(isAutoActivationEnabled()); + ac.setAutoActivationDelay(getAutoActivationDelay()); + ac.setExternalURLHandler(new JavadocUrlHandler()); + ac.setParameterAssistanceEnabled(isParameterAssistanceEnabled()); + ac.setParamChoicesRenderer(new JavaParamListCellRenderer()); + ac.setShowDescWindow(getShowDescWindow()); + ac.install(textArea); + installImpl(textArea, ac); + + textArea.setToolTipSupplier(p); + + Listener listener = new Listener(textArea); + textArea.putClientProperty(PROPERTY_LISTENER, listener); + + JavaParser parser = new JavaParser(textArea); + textArea.putClientProperty(PROPERTY_LANGUAGE_PARSER, parser); + textArea.addParser(parser); + textArea.setToolTipSupplier(p); + + Info info = new Info(textArea, p, parser); + parserToInfoMap.put(parser, info); + + installKeyboardShortcuts(textArea); + log.info("lang support installed"); + textArea.setLinkGenerator(new JavaLinkGenerator(this,p)); + + } + + protected void installCreateLocalVarButton(RSyntaxTextArea textArea) { + // textArea.re + Action action = null; + KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0); + InputMap im = textArea.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); + im.put(ks, ks); + action = new Action() { + + @Override + public void actionPerformed(ActionEvent e) { + + } + + @Override + public void setEnabled(boolean b) { + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + } + + @Override + public void putValue(String key, Object value) { + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Object getValue(String key) { + return null; + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + + } + }; + textArea.getActionMap().put(ks, action); + } + + /** + * Installs extra keyboard shortcuts supported by this language support. + * + * @param textArea The text area to install the shortcuts into. + */ + private void installKeyboardShortcuts(RSyntaxTextArea textArea) { + + InputMap im = textArea.getInputMap(); + ActionMap am = textArea.getActionMap(); + int c = textArea.getToolkit().getMenuShortcutKeyMask(); + int shift = InputEvent.SHIFT_MASK; + + im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, c|shift), "GoToType"); + am.put("GoToType", new GoToMemberAction(JavaOutlineTree.class)); + + } + + + /** + * {@inheritDoc} + */ + public void uninstall(RSyntaxTextArea textArea) { + + uninstallImpl(textArea); + + JavaParser parser = getParser(textArea); + Info info = parserToInfoMap.remove(parser); + if (info!=null) { // Should always be true + parser.removePropertyChangeListener( + JavaParser.PROPERTY_COMPILATION_UNIT, info); + } + textArea.removeParser(parser); + textArea.putClientProperty(PROPERTY_LANGUAGE_PARSER, null); + textArea.setToolTipSupplier(null); + + Object listener = textArea.getClientProperty(PROPERTY_LISTENER); + if (listener instanceof Listener) { // Should always be true + ((Listener)listener).uninstall(); + textArea.putClientProperty(PROPERTY_LISTENER, null); + } + + uninstallKeyboardShortcuts(textArea); + textArea.setLinkGenerator(null); + + } + + + /** + * Uninstalls any keyboard shortcuts specific to this language support. + * + * @param textArea The text area to uninstall the actions from. + */ + private void uninstallKeyboardShortcuts(RSyntaxTextArea textArea) { + + InputMap im = textArea.getInputMap(); + ActionMap am = textArea.getActionMap(); + int c = textArea.getToolkit().getMenuShortcutKeyMask(); + int shift = InputEvent.SHIFT_MASK; + + im.remove(KeyStroke.getKeyStroke(KeyEvent.VK_O, c|shift)); + am.remove("GoToType"); + + } + + + /** + * Information about an import statement to add and where it should be + * added. This is used internally when a class completion is inserted, + * and it needs an import statement added to the source. + */ + private static class ImportToAddInfo { + + public int offs; + public String text; + + public ImportToAddInfo(int offset, String text) { + this.offs = offset; + this.text = text; + } + + } + + + /** + * Manages information about the parsing/auto-completion for a single text + * area. Unlike many simpler language supports, + * JavaLanguageSupport cannot share any information amongst + * instances of RSyntaxTextArea. + */ + private static class Info implements PropertyChangeListener { + + public JavaCompletionProvider provider; + //public JavaParser parser; + + public Info(RSyntaxTextArea textArea, JavaCompletionProvider provider, + JavaParser parser) { + this.provider = provider; + //this.parser = parser; + parser.addPropertyChangeListener( + JavaParser.PROPERTY_COMPILATION_UNIT, this); + } + + /** + * Called when a text area is re-parsed. + * + * @param e The event. + */ + public void propertyChange(PropertyChangeEvent e) { + + String name = e.getPropertyName(); + + if (JavaParser.PROPERTY_COMPILATION_UNIT.equals(name)) { + CompilationUnit cu = (CompilationUnit)e.getNewValue(); +// structureTree.update(file, cu); +// updateTable(); + provider.setCompilationUnit(cu); + } + + } + + } + + + /** + * A hack of AutoCompletion that forces the JavaParser + * to re-parse the document when the user presses ctrl+space. + */ + private class JavaAutoCompletion extends AutoCompletion { + + private RSyntaxTextArea textArea; + private String replacementTextPrefix; + + public JavaAutoCompletion(JavaCompletionProvider provider, + RSyntaxTextArea textArea) { + super(provider); + this.textArea = textArea; + } + + private String getCurrentLineText() { + + int caretPosition = textArea.getCaretPosition(); + Element root = textArea.getDocument().getDefaultRootElement(); + int line= root.getElementIndex(caretPosition); + Element elem = root.getElement(line); + int endOffset = elem.getEndOffset(); + int lineStart = elem.getStartOffset(); + + String text = ""; + try { + text = textArea.getText(lineStart, endOffset-lineStart).trim(); + } catch (BadLocationException e) { + e.printStackTrace(); + } + + return text; + + } + + /** + * Overridden to allow for prepending to the replacement text. This + * allows us to insert fully qualified class names. instead of + * unqualified ones, if necessary (i.e. if the user tries to + * auto-complete javax.swing.text.Document, but they've + * explicitly imported org.w3c.dom.Document - we need to + * insert the fully qualified name in that case). + */ + @Override + protected String getReplacementText(Completion c, Document doc, + int start, int len) { + String text = super.getReplacementText(c, doc, start, len); + if (replacementTextPrefix!=null) { + text = replacementTextPrefix + text; + replacementTextPrefix = null; + } + return text; + } + + /** + * Determines whether the class name being completed has been imported, + * and if it hasn't, returns the import statement that should be added + * for it. Alternatively, if the class hasn't been imported, but a + * class with the same (unqualified) name HAS been imported, this + * method sets things up so the fully-qualified version of this class's + * name is inserted.

+ * + * Thanks to Guilherme Joao Frantz and Jonatas Schuler for helping + * with the patch! + * + * @param c The completion being inserted. + * @return Whether an import was added. + */ + private ImportToAddInfo getShouldAddImport(ClassCompletion cc) { + + String text = getCurrentLineText(); + + // Make sure we're not currently typing an import statement. + if (!text.startsWith("import ")) { + + JavaCompletionProvider provider = (JavaCompletionProvider) + getCompletionProvider(); + CompilationUnit cu = provider.getCompilationUnit(); + int offset = 0; + boolean alreadyImported = false; + + // Try to bail early, if possible. + if (cu==null) { // Can never happen, right? + return null; + } + if ("java.lang".equals(cc.getPackageName())) { + // Package java.lang is "imported" by default. + return null; + } + + String className = cc.getClassName(false); + String fqClassName = cc.getClassName(true); + + // If the completion is in the same package as the source we're + // editing (or both are in the default package), bail. + int lastClassNameDot = fqClassName.lastIndexOf('.'); + boolean ccInPackage = lastClassNameDot>-1; + Package pkg = cu.getPackage(); + if (ccInPackage && pkg!=null) { + String ccPkg = fqClassName.substring(0, lastClassNameDot); + String pkgName = pkg.getName(); + if (ccPkg.equals(pkgName)) { + return null; + } + } + else if (!ccInPackage && pkg==null) { + return null; + } + + // Loop through all import statements. + Iterator i = cu.getImportIterator(); + for (; i.hasNext(); ) { + + ImportDeclaration id = i.next(); + offset = id.getNameEndOffset() + 1; + + // Pulling in static methods, etc. from a class - skip + if (id.isStatic()) { + continue; + } + + // Importing all classes in the package... + else if (id.isWildcard()) { + // NOTE: Class may be in default package... + if (lastClassNameDot>-1) { + String imported = id.getName(); + int dot = imported.lastIndexOf('.'); + String importedPkg = imported.substring(0, dot); + String classPkg = fqClassName.substring(0, lastClassNameDot); + if (importedPkg.equals(classPkg)) { + alreadyImported = true; + break; + } + } + } + + // Importing a single class from a package... + else { + + String fullyImportedClassName = id.getName(); + int dot = fullyImportedClassName.lastIndexOf('.'); + String importedClassName = fullyImportedClassName. + substring(dot + 1); + + // If they explicitly imported a class with the + // same name, but it's in a different package, then + // the user is required to fully-qualify the class + // in their code (if unqualified, it would be + // assumed to be of the type of the qualified + // class). + if (className.equals(importedClassName)) { + offset = -1; // Means "must fully qualify" + if (fqClassName.equals(fullyImportedClassName)) { + alreadyImported = true; + } + break; + } + + } + + } + + // If the class wasn't imported, we'll need to add an + // import statement! + if (!alreadyImported) { + + StringBuilder importToAdd = new StringBuilder(); + + // If there are no previous imports, add the import + // statement after the package line (if any). + if (offset == 0) { + if (pkg!=null) { + offset = pkg.getNameEndOffset() + 1; + // Keep an empty line between package and imports. + importToAdd.append('\n'); + } + } + + // We read through all imports, but didn't find our class. + // Add a new import statement after the last one. + if (offset > -1) { + //System.out.println(classCompletion.getAlreadyEntered(textArea)); + if (offset>0) { + importToAdd.append("\nimport ").append(fqClassName).append(';'); + } + else { + importToAdd.append("import ").append(fqClassName).append(";\n"); + } + // TODO: Determine whether the imports are alphabetical, + // and if so, add the new one alphabetically. + return new ImportToAddInfo(offset, importToAdd.toString()); + } + + // Otherwise, either the class was imported, or a class + // with the same name was explicitly imported. + else { + // Another class with the same name was imported. + // We must insert the fully-qualified class name + // so the compiler resolves the correct class. + int dot = fqClassName.lastIndexOf('.'); + if (dot>-1) { + String pkgName = fqClassName.substring(0, dot+1); + replacementTextPrefix = pkgName; + } + } + + } + + } + + return null; + + } + + /** + * Overridden to handle special cases, because sometimes Java code + * completions will edit more in the source file than just the text + * at the current caret position. + */ + @Override + protected void insertCompletion(Completion c, + boolean typedParamListStartChar) { + + ImportToAddInfo importInfo = null; + + // We special-case class completions because they may add import + // statements to the top of our source file. We don't add the + // (possible) new import statement until after the completion is + // inserted; that way, when we treat it as an atomic undo/redo, + // when the user undoes the completion, the caret stays in the + // code instead of jumping to the import. + if (c instanceof ClassCompletion) { + importInfo = getShouldAddImport((ClassCompletion)c); + if (importInfo!=null) { + textArea.beginAtomicEdit(); + } + } + + try { + super.insertCompletion(c, typedParamListStartChar); + if (importInfo!=null) { + textArea.insert(importInfo.text, importInfo.offs); + } + } finally { + // Be safe and always pair beginAtomicEdit() and endAtomicEdit() + textArea.endAtomicEdit(); + } + + } + + @Override + protected int refreshPopupWindow() { + // Force the parser to re-parse + JavaParser parser = getParser(textArea); + RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); + String style = textArea.getSyntaxEditingStyle(); + parser.parse(doc, style); + return super.refreshPopupWindow(); + } + + } + + + /** + * Listens for various events in a text area editing Java (in particular, + * caret events, so we can track the "active" code block). + */ + private class Listener implements CaretListener, ActionListener { + + private RSyntaxTextArea textArea; + private Timer t; + + public Listener(RSyntaxTextArea textArea) { + this.textArea = textArea; + textArea.addCaretListener(this); + t = new Timer(650, this); + t.setRepeats(false); + } + + public void actionPerformed(ActionEvent e) { + + JavaParser parser = getParser(textArea); + if (parser==null) { + return; // Shouldn't happen + } + CompilationUnit cu = parser.getCompilationUnit(); + + // Highlight the line range of the Java method being edited in the + // gutter. + if (cu != null) { // Should always be true + int dot = textArea.getCaretPosition(); + Point p = cu.getEnclosingMethodRange(dot); + if (p != null) { + try { + int startLine = textArea.getLineOfOffset(p.x); + // Unterminated blocks can end in Integer.MAX_VALUE + int endOffs = Math.min(p.y, + textArea.getDocument().getLength()); + int endLine = textArea.getLineOfOffset(endOffs); + textArea.setActiveLineRange(startLine, endLine); + } catch (BadLocationException ble) { + ble.printStackTrace(); + } + } + else { + textArea.setActiveLineRange(-1, -1); + } + } + + } + + public void caretUpdate(CaretEvent e) { + t.restart(); + } + + /** + * Should be called whenever Java language support is removed from a + * text area. + */ + public void uninstall() { + textArea.removeCaretListener(this); + } + + } + + } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java b/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java index 3846edc9..bd93d3bf 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaLinkGenerator.java @@ -1,268 +1,312 @@ -/* - * 02/17/2013 - * - * Copyright (C) 2013 Robert Futrell - * robert_futrell at users.sourceforge.net - * http://fifesoft.com/rsyntaxtextarea - * - * This library is distributed under a modified BSD license. See the included - * RSTALanguageSupport.License.txt file for details. - */ -package org.fife.rsta.ac.java; - -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import javax.swing.text.BadLocationException; - -import org.fife.rsta.ac.java.rjc.ast.CodeBlock; -import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.FormalParameter; -import org.fife.rsta.ac.java.rjc.ast.LocalVariable; -import org.fife.rsta.ac.java.rjc.ast.Member; -import org.fife.rsta.ac.java.rjc.ast.Method; -import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; -import org.fife.ui.rsyntaxtextarea.LinkGenerator; -import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; -import org.fife.ui.rsyntaxtextarea.SelectRegionLinkGeneratorResult; -import org.fife.ui.rsyntaxtextarea.Token; -import org.fife.ui.rsyntaxtextarea.TokenImpl; - - -/** - * Checks for hyperlink-able tokens under the mouse position when Ctrl is - * pressed (Cmd on OS X). Currently this class only checks for accessible - * members in the current file only (e.g. no members in super classes, no other - * classes on the classpath, etc.). So naturally, there is a lot of room for - * improvement. IDE-style applications, for example, would want to check - * for members in super-classes, and open their source on click events. - * - * @author Robert Futrell - * @version 1.0 - */ -// TODO: Anonymous inner classes probably aren't handled well. -class JavaLinkGenerator implements LinkGenerator { - - private JavaLanguageSupport jls; - - - JavaLinkGenerator(JavaLanguageSupport jls) { - this.jls = jls; - } - - - /** - * Checks if the token at the specified offset is possibly a "click-able" - * region. - * - * @param textArea The text area. - * @param offs The offset, presumably at the mouse position. - * @return A result object. - */ - private IsLinkableCheckResult checkForLinkableToken( - RSyntaxTextArea textArea, int offs) { - - IsLinkableCheckResult result = null; - - if (offs>=0) { - - try { - - int line = textArea.getLineOfOffset(offs); - Token first = textArea.getTokenListForLine(line); - RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); - Token prev = null; - - for (Token t=first; t!=null && t.isPaintable(); t=t.getNextToken()) { - - if (t.containsPosition(offs)) { - - // RSTA's tokens are pooled and re-used, so we must - // defensively make a copy of the one we want to keep! - Token token = new TokenImpl(t); - boolean isMethod = false; - - if (prev==null) { - prev = RSyntaxUtilities.getPreviousImportantToken( - doc, line-1); - } - if (prev!=null && prev.isSingleChar('.')) { - // Not a field or method defined in this class. - break; - } - - Token next = RSyntaxUtilities.getNextImportantToken( - t.getNextToken(), textArea, line); - if (next!=null && next.isSingleChar(Token.SEPARATOR, '(')) { - isMethod = true; - } - - result = new IsLinkableCheckResult(token, isMethod); - break; - - } - - else if (!t.isCommentOrWhitespace()) { - prev = t; - } - - } - - } catch (BadLocationException ble) { - ble.printStackTrace(); // Never happens - } - - } - - return result; - - } - - - /** - * {@inheritDoc} - */ - public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, - int offs) { - - int start = -1; - int end = -1; - - IsLinkableCheckResult result = checkForLinkableToken(textArea, offs); - if (result!=null) { - - JavaParser parser = jls.getParser(textArea); - CompilationUnit cu = parser.getCompilationUnit(); - Token t = result.token; - boolean method = result.method; - - if (cu!=null) { - - TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(offs); - boolean staticFieldsOnly = false; - boolean deepestTypeDec = true; - boolean deepestContainingMemberStatic = false; - while (td!=null && start==-1) { - - // First, check for a local variable in methods/static blocks - if (!method && deepestTypeDec) { - - Iterator i = td.getMemberIterator(); - while (i.hasNext()) { - - Method m = null; // Nasty! Clean this code up - Member member = i.next(); - CodeBlock block = null; - - // Check if a method or static block contains offs - if (member instanceof Method) { - m = (Method)member; - if (m.getBodyContainsOffset(offs) && m.getBody()!=null) { - deepestContainingMemberStatic = m.isStatic(); - block = m.getBody().getDeepestCodeBlockContaining(offs); - } - } - else if (member instanceof CodeBlock) { - block = (CodeBlock)member; - deepestContainingMemberStatic = block.isStatic(); - block = block.getDeepestCodeBlockContaining(offs); - } - - // If so, scan its locals - if (block!=null) { - String varName = t.getLexeme(); - // Local variables first, in reverse order - List locals = block.getLocalVarsBefore(offs); - Collections.reverse(locals); - for (LocalVariable local : locals) { - if (varName.equals(local.getName())) { - start = local.getNameStartOffset(); - end = local.getNameEndOffset(); - } - } - // Then arguments, if any. - if (start==-1 && m!=null) { - for (int j=0; j i = method ? - td.getMethodIterator() : td.getFieldIterator(); - while (i.hasNext()) { - Member member = i.next(); - if (((!deepestContainingMemberStatic && !staticFieldsOnly) || member.isStatic()) && - varName.equals(member.getName())) { - start = member.getNameStartOffset(); - end = member.getNameEndOffset(); - break; - } - } - } - - // If still no match found, check parent type - if (start==-1) { - staticFieldsOnly |= td.isStatic(); - //td = td.isStatic() ? null : td.getParentType(); - td = td.getParentType(); - // Don't check for local vars in parent type methods. - deepestTypeDec = false; - } - - } - - } - - if (start>-1) { - return new SelectRegionLinkGeneratorResult(textArea, t.getOffset(), - start, end); - } - - } - - return null; - - } - - - /** - * The result of checking whether a region of code under the mouse is - * possibly link-able. - */ - private static class IsLinkableCheckResult { - - /** - * The token under the mouse position. - */ - private Token token; - - /** - * Whether the token is a method invocation (as opposed to a local - * variable or field). - */ - private boolean method; - - private IsLinkableCheckResult(Token token, boolean method) { - this.token = token; - this.method = method; - } - - } - - +/* + * 02/17/2013 + * + * Copyright (C) 2013 Robert Futrell + * robert_futrell at users.sourceforge.net + * http://fifesoft.com/rsyntaxtextarea + * + * This library is distributed under a modified BSD license. See the included + * RSTALanguageSupport.License.txt file for details. + */ +package org.fife.rsta.ac.java; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.swing.event.HyperlinkEvent; +import javax.swing.text.BadLocationException; + +import org.apache.log4j.Logger; +import org.fife.rsta.ac.java.rjc.ast.CodeBlock; +import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; +import org.fife.rsta.ac.java.rjc.ast.FormalParameter; +import org.fife.rsta.ac.java.rjc.ast.LocalVariable; +import org.fife.rsta.ac.java.rjc.ast.Member; +import org.fife.rsta.ac.java.rjc.ast.Method; +import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; +import org.fife.ui.rsyntaxtextarea.LinkGenerator; +import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult; +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rsyntaxtextarea.SelectRegionLinkGeneratorResult; +import org.fife.ui.rsyntaxtextarea.Token; +import org.fife.ui.rsyntaxtextarea.TokenImpl; + +import sun.reflect.Reflection; + +/** + * Checks for hyperlink-able tokens under the mouse position when Ctrl is + * pressed (Cmd on OS X). Currently this class only checks for accessible + * members in the current file only (e.g. no members in super classes, no other + * classes on the classpath, etc.). So naturally, there is a lot of room for + * improvement. IDE-style applications, for example, would want to check for + * members in super-classes, and open their source on click events. + * + * @author Robert Futrell + * @version 1.0 + */ +// TODO: Anonymous inner classes probably aren't handled well. +class JavaLinkGenerator implements LinkGenerator { + + private static final Logger log = Logger.getLogger(Reflection.getCallerClass(1)); + + private JavaLanguageSupport jls; + private JavaCompletionProvider javaCompletionProvider; + private SourceCompletionProvider sourceCompletionProvider; + + JavaLinkGenerator(JavaLanguageSupport jls, JavaCompletionProvider p) { + this.jls = jls; + javaCompletionProvider = p; + sourceCompletionProvider = (SourceCompletionProvider)javaCompletionProvider. + getDefaultCompletionProvider(); + } + + /** + * Checks if the token at the specified offset is possibly a "click-able" + * region. + * + * @param textArea + * The text area. + * @param offs + * The offset, presumably at the mouse position. + * @return A result object. + */ + private IsLinkableCheckResult checkForLinkableToken(RSyntaxTextArea textArea, int offs) { + // log.info(offs); + IsLinkableCheckResult result = null; + + if (offs >= 0) { + + try { + + int line = textArea.getLineOfOffset(offs); + Token first = textArea.getTokenListForLine(line); + RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument(); + Token prev = null; + + for (Token t = first; t != null && t.isPaintable(); t = t.getNextToken()) { + + if (t.containsPosition(offs)) { +// log.info(t); + // RSTA's tokens are pooled and re-used, so we must + // defensively make a copy of the one we want to keep! + Token token = new TokenImpl(t); + boolean isMethod = false; + +// if (prev == null) { +// prev = RSyntaxUtilities.getPreviousImportantToken(doc, line - 1); +// } +// log.info(prev); + Token firstE = token; +// ArrayList prevTockens = new ArrayList(); +// prevTockens.add(prev); + int start=firstE.getOffset(); + String alreadyEnteredTextS2 = SourceCompletionProvider.getAlreadyEnteredTextS2(textArea,firstE.getOffset()); + log.info(alreadyEnteredTextS2); + start = start - alreadyEnteredTextS2.length(); +// while (true) { +// Token prevprev = RSyntaxUtilities.getPreviousImportantTokenFromOffs(doc, +// firstÅ.getTextOffset()); +// // log.info(prevprev); +// if (prevprev != null && (prevprev.isSingleChar('=') || prevprev.isSingleChar(';') +// || prevprev.isSingleChar('{')) || prevprev.isSingleChar('}')) { +// break; +// } +// if (prevprev != null) { +//// prevTockens.add(prevprev); +// firstÅ = prevprev; +// start = prevprev.getOffset(); +// }else { +// break; +// +// } +// } + int end = token.getEndOffset(); + Token last= token; +// Token next = RSyntaxUtilities.getNextImportantToken( +// token.getNextToken(), textArea, line); + int maxLength = textArea.getText().length(); + String text2 = textArea.getText(end, Math.min(maxLength, 10)); + if(text2.trim().startsWith("(")) { + int closeBracket = text2.indexOf(')'); + end+=closeBracket+1; + log.info("params added"); + } +// Token next = +// token.getNextToken(); +// if (next!=null && next.isSingleChar(Token.SEPARATOR, '(')) { +// // looking for close bracket +// while(true){ +// +// next = // RSyntaxUtilities.getNextImportantToken( +// next.getNextToken(); //, textArea, line); +// if(next ==null){ +// break; +// }else{ +// if (next!=null && next.isSingleChar(Token.SEPARATOR, ')')){ +// last = next; +// end = next.getEndOffset(); +// break; +// } +// +// } +// } +// } + log.info(firstE); + log.info(last); + IsLinkableCheckResult aa = new IsLinkableCheckResult(token, isMethod); + aa.start = start; // firstÅ.getOffset(); + aa.end = end; + int length = aa.end - aa.start; + log.info("pos: "+aa.start+" "+aa.end); + if(length<1) { + log.info("lengt is negative "+aa.start+" "+aa.end); + }else { + String text = textArea.getText(aa.start, length); + log.info(text); + aa.text = text; + return aa; + } + // log.info(first); + // if (prev != null && prev.isSingleChar('.')) { + // // Not a field or method defined in this class. + // break; + // } + // + // Token next = + // RSyntaxUtilities.getNextImportantToken(t.getNextToken(), + // textArea, line); + // if (next != null && + // next.isSingleChar(Token.SEPARATOR, '(')) { + // isMethod = true; + // } + // + // log.info(token + " " + isMethod); + // result = new IsLinkableCheckResult(token, isMethod); + // break; + + } + + else if (!t.isCommentOrWhitespace()) { + prev = t; + } + + } + + } catch (BadLocationException ble) { + ble.printStackTrace(); // Never happens + } + + } + + return result; + + } + + long lastAccess = -1; + + /** + * {@inheritDoc} + */ + public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offs) { + log.info(offs); + int start = -1; + int end = -1; + + IsLinkableCheckResult result = checkForLinkableToken(textArea, offs); + if (result != null) { + + JavaParser parser = jls.getParser(textArea); + CompilationUnit cu = parser.getCompilationUnit(); +// log.info(cu); + Token t = result.token; + boolean method = result.method; + + if (cu != null) { + + TypeDeclaration td = cu.getDeepestTypeDeclarationAtOffset(offs); +// log.info(td); + boolean staticFieldsOnly = false; + boolean deepestTypeDec = true; + boolean deepestContainingMemberStatic = false; + if(td != null && start == -1) { + Method findCurrencMethod = SourceCompletionProvider.findCurrencMethod(cu, textArea, td, offs); + if (findCurrencMethod != null) { + if(System.currentTimeMillis()-lastAccess < 2000) { + log.info("too often"); + return null; + } + return new LinkGeneratorResult() { + + @Override + public int getSourceOffset() { +// log.info(1); + return 0; + } + + @Override + public HyperlinkEvent execute() { + log.info(result.text); + String text2 = result.text.replace("@", ""); + sourceCompletionProvider.open(cu, result.text, td, findCurrencMethod, text2, offs); +// log.info(2); + return null; + } + }; + } else { + log.info("can't find method"); + } + // First, check for a local variable in methods/static + // blocks + } + + } + + if (start > -1) { + return new SelectRegionLinkGeneratorResult(textArea, t.getOffset(), start, end); + } + + } + + return null; + + } + + private void openClassDeclartion(RSyntaxTextArea textArea, int offs, IsLinkableCheckResult result) { + + } + + /** + * The result of checking whether a region of code under the mouse is + * possibly link-able. + */ + private static class IsLinkableCheckResult { + + public String text; + + /** + * The token under the mouse position. + */ + private Token token; + + private int start; + private int end; + + /** + * Whether the token is a method invocation (as opposed to a local + * variable or field). + */ + private boolean method; + + private IsLinkableCheckResult(Token token, boolean method) { + this.token = token; + this.method = method; + } + + } + } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/java/JavaParser.java b/src/main/java/org/fife/rsta/ac/java/JavaParser.java index ec5d256e..068ae2bd 100644 --- a/src/main/java/org/fife/rsta/ac/java/JavaParser.java +++ b/src/main/java/org/fife/rsta/ac/java/JavaParser.java @@ -15,6 +15,7 @@ import java.io.IOException; import javax.swing.text.Element; +import org.apache.log4j.Logger; import org.fife.io.DocumentReader; import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; import org.fife.rsta.ac.java.rjc.lexer.Scanner; @@ -27,6 +28,8 @@ import org.fife.ui.rsyntaxtextarea.parser.DefaultParserNotice; import org.fife.ui.rsyntaxtextarea.parser.ParseResult; +import sun.reflect.Reflection; + /** * Parses Java code in an RSyntaxTextArea.

@@ -45,12 +48,15 @@ * RSyntaxTextArea.

* * Please keep in mind that this class is a work-in-progress! - * + * * @author Robert Futrell * @version 0.5 */ public class JavaParser extends AbstractParser { + + private static final Logger log = Logger.getLogger(Reflection.getCallerClass(1)); + /** * The property change event that's fired when the document is re-parsed. * Applications can listen for this property change and update themselves @@ -117,6 +123,10 @@ public CompilationUnit getCompilationUnit() { public int getOffset(RSyntaxDocument doc, ParserNotice notice) { Element root = doc.getDefaultRootElement(); Element elem = root.getElement(notice.getLine()); + if (elem == null) { + log.info("NPE " + notice.getLine() + " " + notice); + return -1; + } int offs = elem.getStartOffset() + notice.getColumn(); return offs>=elem.getEndOffset() ? -1 : offs; } diff --git a/src/main/java/org/fife/rsta/ac/java/OpenMember.java b/src/main/java/org/fife/rsta/ac/java/OpenMember.java new file mode 100644 index 00000000..7bc6ee13 --- /dev/null +++ b/src/main/java/org/fife/rsta/ac/java/OpenMember.java @@ -0,0 +1,30 @@ +package org.fife.rsta.ac.java; + +import org.apache.log4j.Logger; +import org.fife.rsta.ac.java.classreader.ClassFile; +import org.fife.rsta.ac.java.classreader.FieldInfo; +import org.fife.rsta.ac.java.classreader.MethodInfo; + +import sun.reflect.Reflection; + +public class OpenMember { + + private static final Logger log = Logger.getLogger(Reflection.getCallerClass(1)); + + public static OpenMember openMember =new OpenMember(); + + + public void openClass(ClassFile classFile) { + log.info(classFile); + } + + public void openMember(MethodInfo methodInfo) { + log.info(methodInfo); + } + + public void openMember(FieldInfo fieldInfoByName) { + log.info(fieldInfoByName); + } + + +} diff --git a/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java b/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java index b913f95b..89941e4f 100644 --- a/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java +++ b/src/main/java/org/fife/rsta/ac/java/SourceCompletionProvider.java @@ -1,1072 +1,1400 @@ -/* - * 03/21/2010 - * - * Copyright (C) 2010 Robert Futrell - * robert_futrell at users.sourceforge.net - * http://fifesoft.com/rsyntaxtextarea - * - * This library is distributed under a modified BSD license. See the included - * RSTALanguageSupport.License.txt file for details. - */ -package org.fife.rsta.ac.java; - -import java.awt.Cursor; -import java.awt.Point; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.JTextComponent; - -import org.fife.rsta.ac.ShorthandCompletionCache; -import org.fife.rsta.ac.java.buildpath.LibraryInfo; -import org.fife.rsta.ac.java.buildpath.SourceLocation; -import org.fife.rsta.ac.java.classreader.ClassFile; -import org.fife.rsta.ac.java.classreader.FieldInfo; -import org.fife.rsta.ac.java.classreader.MemberInfo; -import org.fife.rsta.ac.java.classreader.MethodInfo; -import org.fife.rsta.ac.java.rjc.ast.CodeBlock; -import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; -import org.fife.rsta.ac.java.rjc.ast.Field; -import org.fife.rsta.ac.java.rjc.ast.FormalParameter; -import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; -import org.fife.rsta.ac.java.rjc.ast.LocalVariable; -import org.fife.rsta.ac.java.rjc.ast.Member; -import org.fife.rsta.ac.java.rjc.ast.Method; -import org.fife.rsta.ac.java.rjc.ast.NormalClassDeclaration; -import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; -import org.fife.rsta.ac.java.rjc.lang.Type; -import org.fife.rsta.ac.java.rjc.lang.TypeArgument; -import org.fife.rsta.ac.java.rjc.lang.TypeParameter; -import org.fife.ui.autocomplete.Completion; -import org.fife.ui.autocomplete.DefaultCompletionProvider; -import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; -import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; -import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; -import org.fife.ui.rsyntaxtextarea.Token; - - -/** - * Parses a Java AST for code completions. It currently scans the following: - * - *

- * - * Also, if the caret is inside a method, local variables up to the caret - * position are also returned. - * - * @author Robert Futrell - * @version 1.0 - */ -class SourceCompletionProvider extends DefaultCompletionProvider { - - /** - * The parent completion provider. - */ - private JavaCompletionProvider javaProvider; - - /** - * Used to get information about what classes match imports. - */ - private JarManager jarManager; - - private static final String JAVA_LANG_PACKAGE = "java.lang.*"; - private static final String THIS = "this"; - - //Shorthand completions (templates and comments) - private ShorthandCompletionCache shorthandCache; - /** - * Constructor. - */ - public SourceCompletionProvider() { - this(null); - } - - - /** - * Constructor. - * - * @param jarManager The jar manager for this provider. - */ - public SourceCompletionProvider(JarManager jarManager) { - if (jarManager==null) { - jarManager = new JarManager(); - } - this.jarManager = jarManager; - setParameterizedCompletionParams('(', ", ", ')'); - setAutoActivationRules(false, "."); // Default - only activate after '.' - setParameterChoicesProvider(new SourceParamChoicesProvider()); - } - - - private void addCompletionsForStaticMembers(Set set, - CompilationUnit cu, ClassFile cf, String pkg) { - - // Check us first, so if we override anything, we get the "newest" - // version. - int methodCount = cf.getMethodCount(); - for (int i=0; i set, - CompilationUnit cu, ClassFile cf, String pkg, - Map typeParamMap) { - - // Reset this class's type-arguments-to-type-parameters map, so that - // when methods and fields need to know type arguments, they can query - // for them. - cf.setTypeParamsToTypeArgs(typeParamMap); - - // Check us first, so if we override anything, we get the "newest" - // version. - int methodCount = cf.getMethodCount(); - for (int i=0; i retVal) { - - Type type = var.getType(); - String pkg = cu.getPackageName(); - - if (type.isArray()) { - ClassFile cf = getClassFileFor(cu, "java.lang.Object"); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); - FieldCompletion fc = FieldCompletion. - createLengthCompletion(this, type); - retVal.add(fc); - } - - else if (!type.isBasicType()) { - String typeStr = type.getName(true, false); - ClassFile cf = getClassFileFor(cu, typeStr); - if (cf!=null) { - Map typeParamMap = createTypeParamMap(type, cf); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); - } - } - - } - - - /** - * Adds simple shorthand completions relevant to Java. - * - * @param set The set to add to. - */ - private void addShorthandCompletions(Set set) { - if(shorthandCache != null) { - set.addAll(shorthandCache.getShorthandCompletions()); - } - } - - /** - * Set template completion cache for source completion provider. - * - * @param shorthandCache The new cache. - */ - public void setShorthandCache(ShorthandCompletionCache shorthandCache) { - this.shorthandCache = shorthandCache; - } - - - /** - * Gets the {@link ClassFile} for a class. - * - * @param cu The compilation unit being parsed. - * @param className The name of the class (fully qualified or not). - * @return The {@link ClassFile} for the class, or null if - * cf represents java.lang.Object (or - * if the super class could not be determined). - */ - private ClassFile getClassFileFor(CompilationUnit cu, String className) { - - //System.err.println(">>> Getting class file for: " + className); - if (className==null) { - return null; - } - - ClassFile superClass = null; - - // Determine the fully qualified class to grab - if (!Util.isFullyQualified(className)) { - - // Check in this source file's package first - String pkg = cu.getPackageName(); - if (pkg!=null) { - String temp = pkg + "." + className; - superClass = jarManager.getClassEntry(temp); - } - - // Next, go through the imports (order is important) - if (superClass==null) { - Iterator i = cu.getImportIterator(); - while (i.hasNext()) { - ImportDeclaration id = i.next(); - String imported = id.getName(); - if (imported.endsWith(".*")) { - String temp = imported.substring( - 0, imported.length()-1) + className; - superClass = jarManager.getClassEntry(temp); - if (superClass!=null) { - break; - } - } - else if (imported.endsWith("." + className)) { - superClass = jarManager.getClassEntry(imported); - break; - } - } - } - - // Finally, try java.lang - if (superClass==null) { - String temp = "java.lang." + className; - superClass = jarManager.getClassEntry(temp); - } - - } - - else { - superClass = jarManager.getClassEntry(className); - } - - return superClass; - - } - - - /** - * Adds completions for local variables in a method. - * - * @param set - * @param method - * @param offs The caret's offset into the source. This should be inside - * of method. - */ - private void addLocalVarCompletions(Set set, Method method, - int offs) { - - for (int i=0; iblock. - */ - private void addLocalVarCompletions(Set set, CodeBlock block, - int offs) { - - for (int i=0; ioffs) { - break; - } - } - - } - - - /** - * Adds a jar to read from. - * - * @param info The jar to add. If this is null, then - * the current JVM's main JRE jar (rt.jar, or classes.jar on OS X) - * will be added. If this jar has already been added, adding it - * again will do nothing (except possibly update its attached source - * location). - * @throws IOException If an IO error occurs. - * @see #getJars() - * @see #removeJar(File) - */ - public void addJar(LibraryInfo info) throws IOException { - jarManager.addClassFileSource(info); - } - - - /** - * Checks whether the user is typing a completion for a String member after - * a String literal. - * - * @param comp The text component. - * @param alreadyEntered The text already entered. - * @param cu The compilation unit being parsed. - * @param set The set to add possible completions to. - * @return Whether the user is indeed typing a completion for a String - * literal member. - */ - private boolean checkStringLiteralMember(JTextComponent comp, - String alreadyEntered, - CompilationUnit cu, Set set) { - - boolean stringLiteralMember = false; - - int offs = comp.getCaretPosition() - alreadyEntered.length() - 1; - if (offs>1) { - RSyntaxTextArea textArea = (RSyntaxTextArea)comp; - RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument(); - try { - //System.out.println(doc.charAt(offs) + ", " + doc.charAt(offs+1)); - if (doc.charAt(offs)=='"' && doc.charAt(offs+1)=='.') { - int curLine = textArea.getLineOfOffset(offs); - Token list = textArea.getTokenListForLine(curLine); - Token prevToken = RSyntaxUtilities.getTokenAtOffset(list, offs); - if (prevToken!=null && - prevToken.getType()==Token.LITERAL_STRING_DOUBLE_QUOTE) { - ClassFile cf = getClassFileFor(cu, "java.lang.String"); - addCompletionsForExtendedClass(set, cu, cf, - cu.getPackageName(), null); - stringLiteralMember = true; - } - else { - System.out.println(prevToken); - } - } - } catch (BadLocationException ble) { // Never happens - ble.printStackTrace(); - } - } - - return stringLiteralMember; - - } - - - /** - * Removes all jars from the "build path." - * - * @see #removeJar(File) - * @see #addJar(LibraryInfo) - * @see #getJars() - */ - public void clearJars() { - jarManager.clearClassFileSources(); - // The memory used by the completions can be quite large, so go ahead - // and clear out the completions list so no-longer-needed ones are - // eligible for GC. - clear(); - } - - - /** - * Creates and returns a mapping of type parameters to type arguments. - * - * @param type The type of a variable/field/etc. whose fields/methods/etc. - * are being code completed, as declared in the source. This - * includes type arguments. - * @param cf The ClassFile representing the actual type of - * the variable/field/etc. being code completed - * @return A mapping of type parameter names to type arguments (both - * Strings). - */ - private Map createTypeParamMap(Type type, ClassFile cf) { - Map typeParamMap = null; - List typeArgs = type.getTypeArguments(type.getIdentifierCount()-1); - if (typeArgs!=null) { - typeParamMap = new HashMap(); - List paramTypes = cf.getParamTypes(); - // Should be the same size! Otherwise, the source code has - // too many/too few type arguments listed for this type. - int min = Math.min(paramTypes==null ? 0 : paramTypes.size(), - typeArgs.size()); - for (int i=0; i getCompletionsAt(JTextComponent tc, Point p) { - getCompletionsImpl(tc); // Force loading of completions - return super.getCompletionsAt(tc, p); - } - - - /** - * {@inheritDoc} - */ - @Override - protected List getCompletionsImpl(JTextComponent comp) { - - comp.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - - try { - - completions = new ArrayList();//completions.clear(); - - CompilationUnit cu = javaProvider.getCompilationUnit(); - if (cu==null) { - return completions; // empty - } - - Set set = new TreeSet(); - - // Cut down the list to just those matching what we've typed. - // Note: getAlreadyEnteredText() never returns null - String text = getAlreadyEnteredText(comp); - - // Special case - end of a String literal - boolean stringLiteralMember = checkStringLiteralMember(comp, text, cu, - set); - - // Not after a String literal - regular code completion - if (!stringLiteralMember) { - - // Don't add shorthand completions if they're typing something - // qualified - if (text.indexOf('.')==-1) { - addShorthandCompletions(set); - } - - loadImportCompletions(set, text, cu); - - // Add completions for fully-qualified stuff (e.g. "com.sun.jav") - //long startTime = System.currentTimeMillis(); - jarManager.addCompletions(this, text, set); - //long time = System.currentTimeMillis() - startTime; - //System.out.println("jar completions loaded in: " + time); - - // Loop through all types declared in this source, and provide - // completions depending on in what type/method/etc. the caret's in. - loadCompletionsForCaretPosition(cu, comp, text, set); - - } - - // Do a final sort of all of our completions and we're good to go! - completions = new ArrayList(set); - Collections.sort(completions); - - // Only match based on stuff after the final '.', since that's what is - // displayed for all of our completions. - text = text.substring(text.lastIndexOf('.')+1); - - @SuppressWarnings("unchecked") - int start = Collections.binarySearch(completions, text, comparator); - if (start<0) { - start = -(start+1); - } - else { - // There might be multiple entries with the same input text. - while (start>0 && - comparator.compare(completions.get(start-1), text)==0) { - start--; - } - } - - @SuppressWarnings("unchecked") - int end = Collections.binarySearch(completions, text+'{', comparator); - end = -(end+1); - - return completions.subList(start, end); - - } finally { - comp.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); - } - - } - - - /** - * Returns the jars on the "build path." - * - * @return A list of {@link LibraryInfo}s. Modifying a - * LibraryInfo in this list will have no effect on - * this completion provider; in order to do that, you must re-add - * the jar via {@link #addJar(LibraryInfo)}. If there are - * no jars on the "build path," this will be an empty list. - * @see #addJar(LibraryInfo) - */ - public List getJars() { - return jarManager.getClassFileSources(); - } - - - -public SourceLocation getSourceLocForClass(String className) { - return jarManager.getSourceLocForClass(className); -} - - /** - * Returns whether a method defined by a super class is accessible to - * this class. - * - * @param info Information about the member. - * @param pkg The package of the source currently being parsed. - * @return Whether or not the method is accessible. - */ - private boolean isAccessible(MemberInfo info, String pkg) { - - boolean accessible = false; - int access = info.getAccessFlags(); - - if (org.fife.rsta.ac.java.classreader.Util.isPublic(access) || - org.fife.rsta.ac.java.classreader.Util.isProtected(access)) { - accessible = true; - } - else if (org.fife.rsta.ac.java.classreader.Util.isDefault(access)) { - String pkg2 = info.getClassFile().getPackageName(); - accessible = (pkg==null && pkg2==null) || - (pkg!=null && pkg.equals(pkg2)); - } - - return accessible; - - } - - - /** - * {@inheritDoc} - */ - @Override - protected boolean isValidChar(char ch) { - return Character.isJavaIdentifierPart(ch) || ch=='.'; - } - - - /** - * Loads completions based on the current caret location in the source. In - * other words: - * - *
    - *
  • If the caret is anywhere in a class, the names of all methods and - * fields in the class are loaded. Methods and fields in super - * classes are also loaded. TODO: Get super methods/fields added - * correctly by access! - *
  • If the caret is in a field, local variables currently accessible - * are loaded. - *
- * - * @param cu - * @param comp - * @param alreadyEntered - * @param retVal - */ - private void loadCompletionsForCaretPosition(CompilationUnit cu, - JTextComponent comp, String alreadyEntered, Set retVal) { - - // Get completions for all fields and methods of all type declarations. - - //long startTime = System.currentTimeMillis(); - int caret = comp.getCaretPosition(); - //List temp = new ArrayList(); - int start, end; - - int lastDot = alreadyEntered.lastIndexOf('.'); - boolean qualified = lastDot>-1; - String prefix = qualified ? alreadyEntered.substring(0, lastDot) : null; - - Iterator i = cu.getTypeDeclarationIterator(); - while (i.hasNext()) { - - TypeDeclaration td = i.next(); - start = td.getBodyStartOffset(); - end = td.getBodyEndOffset(); - - if (caret>start && caret<=end) { - loadCompletionsForCaretPosition(cu, comp, alreadyEntered, - retVal, td, prefix, caret); - } - - else if (caret - *
  • If the caret is anywhere in a class, the names of all methods and - * fields in the class are loaded. Methods and fields in super - * classes are also loaded. TODO: Get super methods/fields added - * correctly by access! - *
  • If the caret is in a field, local variables currently accessible - * are loaded. - * - * - * @param cu - * @param comp - * @param alreadyEntered - * @param retVal - */ - private void loadCompletionsForCaretPosition(CompilationUnit cu, - JTextComponent comp, String alreadyEntered, Set retVal, - TypeDeclaration td, String prefix, int caret) { - - // Do any child types first, so if any vars, etc. have duplicate names, - // we pick up the one "closest" to us first. - for (int i=0; i typeParamMap = new HashMap(); - if (td instanceof NormalClassDeclaration) { - NormalClassDeclaration ncd = (NormalClassDeclaration)td; - List typeParams = ncd.getTypeParameters(); - if (typeParams!=null) { - for (TypeParameter typeParam : typeParams) { - String typeVar = typeParam.getName(); - // For non-qualified completions, use type var name. - typeParamMap.put(typeVar, typeVar); - } - } - } - - // Get completions for this class's methods, fields and local - // vars. Do this before checking super classes so that, if - // we overrode anything, we get the "newest" version. - String pkg = cu.getPackageName(); - Iterator j = td.getMemberIterator(); - while (j.hasNext()) { - Member m = j.next(); - if (m instanceof Method) { - Method method = (Method)m; - if (prefix==null || THIS.equals(prefix)) { - retVal.add(new MethodCompletion(this, method)); - } - if (caret>=method.getBodyStartOffset() && caretnull if - * none. - * @param prefix The text up to the current caret position. This is - * guaranteed to be non-null not equal to - * "this". - * @param offs The offset of the caret in the document. - */ - private void loadCompletionsForCaretPositionQualified(CompilationUnit cu, - String alreadyEntered, Set retVal, - TypeDeclaration td, Method currentMethod, String prefix, int offs) { - - // TODO: Remove this restriction. - int dot = prefix.indexOf('.'); - if (dot>-1) { - System.out.println("[DEBUG]: Qualified non-this completions currently only go 1 level deep"); - return; - } - - // TODO: Remove this restriction. - else if (!prefix.matches("[A-Za-z_][A-Za-z0-9_\\$]*")) { - System.out.println("[DEBUG]: Only identifier non-this completions are currently supported"); - return; - } - - String pkg = cu.getPackageName(); - boolean matched = false; - - for (Iterator j=td.getMemberIterator(); j.hasNext(); ) { - - Member m = j.next(); - - // The prefix might be a field in the local class. - if (m instanceof Field) { - - Field field = (Field)m; - - if (field.getName().equals(prefix)) { - //System.out.println("FOUND: " + prefix + " (" + pkg + ")"); - Type type = field.getType(); - if (type.isArray()) { - ClassFile cf = getClassFileFor(cu, "java.lang.Object"); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); - FieldCompletion fc = FieldCompletion. - createLengthCompletion(this, type); - retVal.add(fc); - } - else if (!type.isBasicType()) { - String typeStr = type.getName(true, false); - ClassFile cf = getClassFileFor(cu, typeStr); - // Add completions for extended class type chain - if (cf!=null) { - Map typeParamMap = createTypeParamMap(type, cf); - addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); - // Add completions for all implemented interfaces - // TODO: Only do this if type is abstract! - for (int i=0; i imports = cu.getImports(); - List matches = jarManager.getClassesWithUnqualifiedName( - prefix, imports); - if (matches!=null) { - for (int i=0; i retVal, - TypeDeclaration td, CodeBlock block, String prefix, int offs) { - - boolean found = false; - - for (int i=0; ioffs) { - break; - } - } - - } - - - /** - * Loads completions for a single import statement. - * - * @param importStr The import statement. - * @param pkgName The package of the source currently being parsed. - */ - private void loadCompletionsForImport(Set set, - String importStr, String pkgName) { - - if (importStr.endsWith(".*")) { - String pkg = importStr.substring(0, importStr.length()-2); - boolean inPkg = pkg.equals(pkgName); - List classes= jarManager.getClassesInPackage(pkg, inPkg); - for (ClassFile cf : classes) { - set.add(new ClassCompletion(this, cf)); - } - } - - else { - ClassFile cf = jarManager.getClassEntry(importStr); - if (cf!=null) { - set.add(new ClassCompletion(this, cf)); - } - } - - } - - - /** - * Loads completions for all import statements. - * - * @param cu The compilation unit being parsed. - */ - private void loadImportCompletions(Set set, String text, - CompilationUnit cu) { - - // Fully-qualified completions are handled elsewhere, so no need to - // duplicate the work here - if (text.indexOf('.')>-1) { - return; - } - - //long startTime = System.currentTimeMillis(); - - String pkgName = cu.getPackageName(); - loadCompletionsForImport(set, JAVA_LANG_PACKAGE, pkgName); - for (Iterator i=cu.getImportIterator(); i.hasNext(); ) { - ImportDeclaration id = i.next(); - String name = id.getName(); - if (!JAVA_LANG_PACKAGE.equals(name)) { - loadCompletionsForImport(set, name, pkgName); - } - } -// Collections.sort(completions); - - //long time = System.currentTimeMillis() - startTime; - //System.out.println("imports loaded in: " + time); - - } - - - /** - * Removes a jar from the "build path." - * - * @param jar The jar to remove. - * @return Whether the jar was removed. This will be false - * if the jar was not on the build path. - * @see #addJar(LibraryInfo) - * @see #getJars() - * @see #clearJars() - */ - public boolean removeJar(File jar) { - boolean removed = jarManager.removeClassFileSource(jar); - // The memory used by the completions can be quite large, so go ahead - // and clear out the completions list so no-longer-needed ones are - // eligible for GC. - if (removed) { - clear(); - } - return removed; - } - - - /** - * Sets the parent Java provider. - * - * @param javaProvider The parent completion provider. - */ - void setJavaProvider(JavaCompletionProvider javaProvider) { - this.javaProvider = javaProvider; - } - - +/* + * 03/21/2010 + * + * Copyright (C) 2010 Robert Futrell + * robert_futrell at users.sourceforge.net + * http://fifesoft.com/rsyntaxtextarea + * + * This library is distributed under a modified BSD license. See the included + * RSTALanguageSupport.License.txt file for details. + */ +package org.fife.rsta.ac.java; + +import java.awt.Cursor; +import java.awt.Point; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import javax.swing.JComponent; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import javax.swing.text.Element; +import javax.swing.text.JTextComponent; +import javax.swing.text.Segment; + +import org.apache.log4j.Logger; +import org.fife.rsta.ac.ShorthandCompletionCache; +import org.fife.rsta.ac.java.buildpath.LibraryInfo; +import org.fife.rsta.ac.java.buildpath.SourceLocation; +import org.fife.rsta.ac.java.classreader.ClassFile; +import org.fife.rsta.ac.java.classreader.FieldInfo; +import org.fife.rsta.ac.java.classreader.MemberInfo; +import org.fife.rsta.ac.java.classreader.MethodInfo; +import org.fife.rsta.ac.java.rjc.ast.CodeBlock; +import org.fife.rsta.ac.java.rjc.ast.CompilationUnit; +import org.fife.rsta.ac.java.rjc.ast.Field; +import org.fife.rsta.ac.java.rjc.ast.FormalParameter; +import org.fife.rsta.ac.java.rjc.ast.ImportDeclaration; +import org.fife.rsta.ac.java.rjc.ast.LocalVariable; +import org.fife.rsta.ac.java.rjc.ast.Member; +import org.fife.rsta.ac.java.rjc.ast.Method; +import org.fife.rsta.ac.java.rjc.ast.NormalClassDeclaration; +import org.fife.rsta.ac.java.rjc.ast.TypeDeclaration; +import org.fife.rsta.ac.java.rjc.lang.Type; +import org.fife.rsta.ac.java.rjc.lang.TypeArgument; +import org.fife.rsta.ac.java.rjc.lang.TypeParameter; +import org.fife.ui.autocomplete.Completion; +import org.fife.ui.autocomplete.DefaultCompletionProvider; +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; +import org.fife.ui.rsyntaxtextarea.Token; + +import sun.reflect.Reflection; + +/** + * Parses a Java AST for code completions. It currently scans the following: + * + *
      + *
    • Import statements + *
    • Method names + *
    • Field names + *
    + * + * Also, if the caret is inside a method, local variables up to the caret + * position are also returned. + * + * @author Robert Futrell + * @version 1.0 + */ +public class SourceCompletionProvider extends DefaultCompletionProvider { + + private static final Logger log = Logger.getLogger(Reflection.getCallerClass(1)); + + public static boolean loadPrivateMemberAlways = true; + + /** + * The parent completion provider. + */ + private JavaCompletionProvider javaProvider; + + /** + * Used to get information about what classes match imports. + */ + private JarManager jarManager; + + private static final String JAVA_LANG_PACKAGE = "java.lang.*"; + private static final String THIS = "this"; + + // Shorthand completions (templates and comments) + private ShorthandCompletionCache shorthandCache; + + /** + * Constructor. + */ + public SourceCompletionProvider() { + this(null); + } + + /** + * Constructor. + * + * @param jarManager + * The jar manager for this provider. + */ + public SourceCompletionProvider(JarManager jarManager) { + if (jarManager == null) { + jarManager = new JarManager(); + } + this.jarManager = jarManager; + setParameterizedCompletionParams('(', ", ", ')'); + setAutoActivationRules(false, "."); // Default - only activate after '.' + setParameterChoicesProvider(new SourceParamChoicesProvider()); + } + + private void addCompletionsForStaticMembers(Set set, CompilationUnit cu, ClassFile cf, String pkg) { + + // Check us first, so if we override anything, we get the "newest" + // version. + int methodCount = cf.getMethodCount(); + for (int i = 0; i < methodCount; i++) { + MethodInfo info = cf.getMethodInfo(i); + if (isAccessible(info, pkg) && info.isStatic()) { + MethodCompletion mc = new MethodCompletion(this, info); + set.add(mc); + } + } + + int fieldCount = cf.getFieldCount(); + for (int i = 0; i < fieldCount; i++) { + FieldInfo info = cf.getFieldInfo(i); + if (isAccessible(info, pkg) && info.isStatic()) { + FieldCompletion fc = new FieldCompletion(this, info); + set.add(fc); + } + } + + ClassFile superClass = getClassFileFor(cu, cf.getSuperClassName(true)); + if (superClass != null) { + addCompletionsForStaticMembers(set, cu, superClass, pkg); + } + + } + + /** + * Adds completions for accessible methods and fields of super classes. This + * is only called when the caret is inside of a class. TODO: Handle + * accessibility correctly! + * + * @param set + * @param cu + * The compilation unit. + * @param cf + * A class in the chain of classes that a type being parsed + * inherits from. + * @param pkg + * The package of the source being parsed. + * @param typeParamMap + * A mapping of type parameters to type arguments for the object + * whose fields/methods/etc. are currently being code-completed. + */ + private void addCompletionsForExtendedClass(Set set, CompilationUnit cu, ClassFile cf, String pkg, + Map typeParamMap) { + + // Reset this class's type-arguments-to-type-parameters map, so that + // when methods and fields need to know type arguments, they can query + // for them. + cf.setTypeParamsToTypeArgs(typeParamMap); + + // Check us first, so if we override anything, we get the "newest" + // version. + int methodCount = cf.getMethodCount(); + for (int i = 0; i < methodCount; i++) { + MethodInfo info = cf.getMethodInfo(i); + // Don't show constructors + if (isAccessible(info, pkg) && !info.isConstructor()) { + MethodCompletion mc = new MethodCompletion(this, info); + set.add(mc); + } + } + + int fieldCount = cf.getFieldCount(); + for (int i = 0; i < fieldCount; i++) { + FieldInfo info = cf.getFieldInfo(i); + if (isAccessible(info, pkg)) { + FieldCompletion fc = new FieldCompletion(this, info); + set.add(fc); + } + } + + // Add completions for any non-overridden super-class methods. + ClassFile superClass = getClassFileFor(cu, cf.getSuperClassName(true)); + if (superClass != null) { + addCompletionsForExtendedClass(set, cu, superClass, pkg, typeParamMap); + } + + // Add completions for any interface methods, in case this class is + // abstract and hasn't implemented some of them yet. + // TODO: Do this only if "top-level" class is declared abstract + for (int i = 0; i < cf.getImplementedInterfaceCount(); i++) { + String inter = cf.getImplementedInterfaceName(i, true); + cf = getClassFileFor(cu, inter); + addCompletionsForExtendedClass(set, cu, cf, pkg, typeParamMap); + } + + } + + /** + * Adds completions for all methods and public fields of a local variable. + * This will add nothing if the local variable is a primitive type. + * + * @param cu + * The compilation unit being parsed. + * @param var + * The local variable. + * @param retVal + * The set to add completions to. + */ + private void addCompletionsForLocalVarsMethods(CompilationUnit cu, LocalVariable var, Set retVal) { + + Type type = var.getType(); + String pkg = cu.getPackageName(); + + if (type.isArray()) { + ClassFile cf = getClassFileFor(cu, "java.lang.Object"); + addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); + FieldCompletion fc = FieldCompletion.createLengthCompletion(this, type); + retVal.add(fc); + } + + else if (!type.isBasicType()) { + String typeStr = type.getName(true, false); + ClassFile cf = getClassFileFor(cu, typeStr); + if (cf != null) { + Map typeParamMap = createTypeParamMap(type, cf); + addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); + } + } + + } + + /** + * Adds simple shorthand completions relevant to Java. + * + * @param set + * The set to add to. + */ + private void addShorthandCompletions(Set set) { + if (shorthandCache != null) { + set.addAll(shorthandCache.getShorthandCompletions()); + } + } + + /** + * Set template completion cache for source completion provider. + * + * @param shorthandCache + * The new cache. + */ + public void setShorthandCache(ShorthandCompletionCache shorthandCache) { + this.shorthandCache = shorthandCache; + } + + /** + * Gets the {@link ClassFile} for a class. + * + * @param cu + * The compilation unit being parsed. + * @param className + * The name of the class (fully qualified or not). + * @return The {@link ClassFile} for the class, or null if + * cf represents java.lang.Object (or if + * the super class could not be determined). + */ + private ClassFile getClassFileFor(CompilationUnit cu, String className) { + + // System.err.println(">>> Getting class file for: " + className); + if (className == null) { + return null; + } + + ClassFile superClass = null; + + // Determine the fully qualified class to grab + if (!Util.isFullyQualified(className)) { + + // Check in this source file's package first + String pkg = cu.getPackageName(); + if (pkg != null) { + String temp = pkg + "." + className; + superClass = jarManager.getClassEntry(temp); + } + + // Next, go through the imports (order is important) + if (superClass == null) { + Iterator i = cu.getImportIterator(); + while (i.hasNext()) { + ImportDeclaration id = i.next(); + String imported = id.getName(); + if (imported.endsWith(".*")) { + String temp = imported.substring(0, imported.length() - 1) + className; + superClass = jarManager.getClassEntry(temp); + if (superClass != null) { + break; + } + } else if (imported.endsWith("." + className)) { + superClass = jarManager.getClassEntry(imported); + break; + } + } + } + + // Finally, try java.lang + if (superClass == null) { + String temp = "java.lang." + className; + superClass = jarManager.getClassEntry(temp); + } + + } else { + superClass = jarManager.getClassEntry(className); + } + + return superClass; + + } + + /** + * Adds completions for local variables in a method. + * + * @param set + * @param method + * @param offs + * The caret's offset into the source. This should be inside of + * method. + */ + private void addLocalVarCompletions(Set set, Method method, int offs) { + + for (int i = 0; i < method.getParameterCount(); i++) { + FormalParameter param = method.getParameter(i); + set.add(new LocalVariableCompletion(this, param)); + } + + CodeBlock body = method.getBody(); + if (body != null) { + addLocalVarCompletions(set, body, offs); + } + + } + + /** + * Adds completions for local variables in a code block inside of a method. + * + * @param set + * @param block + * The code block. + * @param offs + * The caret's offset into the source. This should be inside of + * block. + */ + private void addLocalVarCompletions(Set set, CodeBlock block, int offs) { + + for (int i = 0; i < block.getLocalVarCount(); i++) { + LocalVariable var = block.getLocalVar(i); + if (var.getNameEndOffset() <= offs) { + set.add(new LocalVariableCompletion(this, var)); + } else { // This and all following declared after offs + break; + } + } + + for (int i = 0; i < block.getChildBlockCount(); i++) { + CodeBlock child = block.getChildBlock(i); + if (child.containsOffset(offs)) { + addLocalVarCompletions(set, child, offs); + break; // All other blocks are past this one + } + // If we've reached a block that's past the offset we're + // searching for... + else if (child.getNameStartOffset() > offs) { + break; + } + } + + } + + /** + * Adds a jar to read from. + * + * @param info + * The jar to add. If this is null, then the current + * JVM's main JRE jar (rt.jar, or classes.jar on OS X) will be + * added. If this jar has already been added, adding it again + * will do nothing (except possibly update its attached source + * location). + * @throws IOException + * If an IO error occurs. + * @see #getJars() + * @see #removeJar(File) + */ + public void addJar(LibraryInfo info) throws IOException { + jarManager.addClassFileSource(info); + } + + /** + * Checks whether the user is typing a completion for a String member after + * a String literal. + * + * @param comp + * The text component. + * @param alreadyEntered + * The text already entered. + * @param cu + * The compilation unit being parsed. + * @param set + * The set to add possible completions to. + * @return Whether the user is indeed typing a completion for a String + * literal member. + */ + private boolean checkStringLiteralMember(JTextComponent comp, String alreadyEntered, CompilationUnit cu, + Set set) { + + boolean stringLiteralMember = false; + + int offs = comp.getCaretPosition() - alreadyEntered.length() - 1; + if (offs > 1) { + RSyntaxTextArea textArea = (RSyntaxTextArea) comp; + RSyntaxDocument doc = (RSyntaxDocument) textArea.getDocument(); + try { + // log.info(doc.charAt(offs) + ", " + + // doc.charAt(offs+1)); + if (doc.charAt(offs) == '"' && doc.charAt(offs + 1) == '.') { + int curLine = textArea.getLineOfOffset(offs); + Token list = textArea.getTokenListForLine(curLine); + Token prevToken = RSyntaxUtilities.getTokenAtOffset(list, offs); + if (prevToken != null && prevToken.getType() == Token.LITERAL_STRING_DOUBLE_QUOTE) { + ClassFile cf = getClassFileFor(cu, "java.lang.String"); + addCompletionsForExtendedClass(set, cu, cf, cu.getPackageName(), null); + stringLiteralMember = true; + } else { + log.info(prevToken); + } + } + } catch (BadLocationException ble) { // Never happens + ble.printStackTrace(); + } + } + + return stringLiteralMember; + + } + + /** + * Removes all jars from the "build path." + * + * @see #removeJar(File) + * @see #addJar(LibraryInfo) + * @see #getJars() + */ + public void clearJars() { + jarManager.clearClassFileSources(); + // The memory used by the completions can be quite large, so go ahead + // and clear out the completions list so no-longer-needed ones are + // eligible for GC. + clear(); + } + + /** + * Creates and returns a mapping of type parameters to type arguments. + * + * @param type + * The type of a variable/field/etc. whose fields/methods/etc. + * are being code completed, as declared in the source. This + * includes type arguments. + * @param cf + * The ClassFile representing the actual type of the + * variable/field/etc. being code completed + * @return A mapping of type parameter names to type arguments (both + * Strings). + */ + private Map createTypeParamMap(Type type, ClassFile cf) { + Map typeParamMap = null; + List typeArgs = type.getTypeArguments(type.getIdentifierCount() - 1); + if (typeArgs != null) { + typeParamMap = new HashMap(); + List paramTypes = cf.getParamTypes(); + // Should be the same size! Otherwise, the source code has + // too many/too few type arguments listed for this type. + int min = Math.min(paramTypes == null ? 0 : paramTypes.size(), typeArgs.size()); + for (int i = 0; i < min; i++) { + TypeArgument typeArg = typeArgs.get(i); + typeParamMap.put(paramTypes.get(i), typeArg.toString()); + } + } + return typeParamMap; + } + + /** + * {@inheritDoc} + */ + @Override + public List getCompletionsAt(JTextComponent tc, Point p) { + getCompletionsImpl(tc); // Force loading of completions + return super.getCompletionsAt(tc, p); + } + + /** + * {@inheritDoc} + */ + @Override + protected List getCompletionsImpl(JTextComponent comp) { + + comp.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + try { + + completions = new ArrayList();// completions.clear(); + + CompilationUnit cu = javaProvider.getCompilationUnit(); + if (cu == null) { + return completions; // empty + } + + Set set = new TreeSet(); + + // Cut down the list to just those matching what we've typed. + // Note: getAlreadyEnteredText() never returns null + String text = getAlreadyEnteredText(comp); + // log.info(text); + // Special case - end of a String literal + boolean stringLiteralMember = checkStringLiteralMember(comp, text, cu, set); + + // Not after a String literal - regular code completion + if (!stringLiteralMember) { + + // Don't add shorthand completions if they're typing something + // qualified + if (text.indexOf('.') == -1) { + addShorthandCompletions(set); + } + + loadImportCompletions(set, text, cu); + + // Add completions for fully-qualified stuff (e.g. + // "com.sun.jav") + // long startTime = System.currentTimeMillis(); + jarManager.addCompletions(this, text, set); + // long time = System.currentTimeMillis() - startTime; + // log.info("jar completions loaded in: " + time); + + // Loop through all types declared in this source, and provide + // completions depending on in what type/method/etc. the caret's + // in. + loadCompletionsForCaretPosition(cu, comp, text, set); + + } + + // Do a final sort of all of our completions and we're good to go! + completions = new ArrayList(set); + Collections.sort(completions); + + // Only match based on stuff after the final '.', since that's what + // is + // displayed for all of our completions. + text = text.substring(text.lastIndexOf('.') + 1); + + @SuppressWarnings("unchecked") + int start = Collections.binarySearch(completions, text, comparator); + if (start < 0) { + start = -(start + 1); + } else { + // There might be multiple entries with the same input text. + while (start > 0 && comparator.compare(completions.get(start - 1), text) == 0) { + start--; + } + } + + @SuppressWarnings("unchecked") + int end = Collections.binarySearch(completions, text + '{', comparator); + end = -(end + 1); + + return completions.subList(start, end); + + } finally { + comp.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); + } + + } + + @Override + public String getAlreadyEnteredText(JTextComponent comp) { + return getAlreadyEnteredTextS(comp); + } + + public static String getAlreadyEnteredTextS(JTextComponent comp) { + + Document doc = comp.getDocument(); + + int dot = comp.getCaretPosition(); + Element root = doc.getDefaultRootElement(); + int index = root.getElementIndex(dot); + Element elem = root.getElement(index); + int start = elem.getStartOffset(); + int len = dot - start; + Segment seg = new Segment(new char[10000], 0, 0); + try { + doc.getText(start, len, seg); + } catch (BadLocationException ble) { + ble.printStackTrace(); + return EMPTY_STRING; + } + + int segEnd = seg.offset + len; + start = segEnd - 1; + while (start >= seg.offset && isValidChar2(seg.array[start])) { + start--; + } + start++; + + len = segEnd - start; + if (len == 0) + return EMPTY_STRING; + String result = new String(seg.array, start, len); + result = result.trim().replace("\t", ""); + return result; + + } + + public static String getAlreadyEnteredTextS2(JTextComponent comp, int dot) { + + Document doc = comp.getDocument(); + +// int dot = comp.getCaretPosition(); + Element root = doc.getDefaultRootElement(); + int index = root.getElementIndex(dot); + Element elem = root.getElement(index); + int start = elem.getStartOffset(); + int len = dot - start; + Segment seg = new Segment(new char[10000], 0, 0); + try { + doc.getText(start, len, seg); + } catch (BadLocationException ble) { + ble.printStackTrace(); + return EMPTY_STRING; + } + + int segEnd = seg.offset + len; + start = segEnd - 1; + while (start >= seg.offset && isValidChar2(seg.array[start])) { + start--; + } + start++; + + len = segEnd - start; + if (len == 0) + return EMPTY_STRING; + String result = new String(seg.array, start, len); + result = result.trim().replace("\t", ""); + return result; + + } + + /** + * Returns the jars on the "build path." + * + * @return A list of {@link LibraryInfo}s. Modifying a + * LibraryInfo in this list will have no effect on this + * completion provider; in order to do that, you must re-add the jar + * via {@link #addJar(LibraryInfo)}. If there are no jars on the + * "build path," this will be an empty list. + * @see #addJar(LibraryInfo) + */ + public List getJars() { + return jarManager.getClassFileSources(); + } + + public SourceLocation getSourceLocForClass(String className) { + return jarManager.getSourceLocForClass(className); + } + + /** + * Returns whether a method defined by a super class is accessible to this + * class. + * + * @param info + * Information about the member. + * @param pkg + * The package of the source currently being parsed. + * @return Whether or not the method is accessible. + */ + private boolean isAccessible(MemberInfo info, String pkg) { + if (loadPrivateMemberAlways) { + return true; + } + boolean accessible = false; + int access = info.getAccessFlags(); + + if (org.fife.rsta.ac.java.classreader.Util.isPublic(access) + || org.fife.rsta.ac.java.classreader.Util.isProtected(access)) { + accessible = true; + } else if (org.fife.rsta.ac.java.classreader.Util.isDefault(access)) { + String pkg2 = info.getClassFile().getPackageName(); + accessible = (pkg == null && pkg2 == null) || (pkg != null && pkg.equals(pkg2)); + } + + return accessible; + + } + + protected static boolean isValidChar2(char ch) { + boolean res = ch != ';' && ch != '=' && ch != '{' && ch != '}'; + if (!res) { + // log.info(ch); + } + return res; + } + + /** + * {@inheritDoc} + */ + @Override + protected boolean isValidChar(char ch) { + return Character.isJavaIdentifierPart(ch) || ch == '.'; + } + + /** + * Loads completions based on the current caret location in the source. In + * other words: + * + *
      + *
    • If the caret is anywhere in a class, the names of all methods and + * fields in the class are loaded. Methods and fields in super classes are + * also loaded. TODO: Get super methods/fields added correctly by access! + *
    • If the caret is in a field, local variables currently accessible are + * loaded. + *
    + * + * @param cu + * @param comp + * @param alreadyEntered + * @param retVal + */ + private void loadCompletionsForCaretPosition(CompilationUnit cu, JTextComponent comp, String alreadyEntered, + Set retVal) { + + // Get completions for all fields and methods of all type declarations. + + // long startTime = System.currentTimeMillis(); + int caret = comp.getCaretPosition(); + // List temp = new ArrayList(); + int start, end; + + int lastDot = alreadyEntered.lastIndexOf('.'); + boolean qualified = lastDot > -1; + String prefix = qualified ? alreadyEntered.substring(0, lastDot) : null; + + Iterator i = cu.getTypeDeclarationIterator(); + while (i.hasNext()) { + + TypeDeclaration td = i.next(); + start = td.getBodyStartOffset(); + end = td.getBodyEndOffset(); + + if (caret > start && caret <= end) { + loadCompletionsForCaretPosition(cu, comp, alreadyEntered, retVal, td, prefix, caret); + } + + else if (caret < start) { + break; // We've passed any type declarations we could be in + } + + } + + // long time = System.currentTimeMillis() - startTime; + // log.info("methods/fields/localvars loaded in: " + time); + + } + + public static Method findCurrencMethod(CompilationUnit cu, JTextComponent comp, + TypeDeclaration td, int caret) { + Iterator j = td.getMemberIterator(); + while (j.hasNext()) { + Member m = j.next(); + if (m instanceof Method) { + Method method = (Method) m; + if (caret >= method.getBodyStartOffset() && caret < method.getBodyEndOffset()) { + // Don't add completions for local vars if there is + // a prefix, even "this". + return method; + } + } + } + return null; + } + + + /** + * Loads completions based on the current caret location in the source. This + * method is called when the caret is found to be in a specific type + * declaration. This method checks if the caret is in a child type + * declaration first, then adds completions for itself next. + * + *
      + *
    • If the caret is anywhere in a class, the names of all methods and + * fields in the class are loaded. Methods and fields in super classes are + * also loaded. TODO: Get super methods/fields added correctly by access! + *
    • If the caret is in a field, local variables currently accessible are + * loaded. + *
    + * + * @param cu + * @param comp + * @param alreadyEntered + * @param retVal + */ + private void loadCompletionsForCaretPosition(CompilationUnit cu, JTextComponent comp, String alreadyEntered, + Set retVal, TypeDeclaration td, String prefix, int caret) { + + // Do any child types first, so if any vars, etc. have duplicate names, + // we pick up the one "closest" to us first. + for (int i = 0; i < td.getChildTypeCount(); i++) { + TypeDeclaration childType = td.getChildType(i); + loadCompletionsForCaretPosition(cu, comp, alreadyEntered, retVal, childType, prefix, caret); + } + + { + Map typeParamMap = new HashMap(); + if (td instanceof NormalClassDeclaration) { + NormalClassDeclaration ncd = (NormalClassDeclaration) td; + List typeParams = ncd.getTypeParameters(); + if (typeParams != null) { + for (TypeParameter typeParam : typeParams) { + String typeVar = typeParam.getName(); + // For non-qualified completions, use type var name. + typeParamMap.put(typeVar, typeVar); + } + } + } + } + // Get completions for this class's methods, fields and local + // vars. Do this before checking super classes so that, if + // we overrode anything, we get the "newest" version. + Method currentMethod = findCurrencMethod(cu, comp, td,caret); + if(currentMethod!=null){ + if (prefix == null) { + addLocalVarCompletions(retVal, currentMethod, caret); + } + } + { + Iterator j = td.getMemberIterator(); + while (j.hasNext()) { + Member m = j.next(); + if (m instanceof Method) { + Method method = (Method) m; + if (prefix == null || THIS.equals(prefix)) { + retVal.add(new MethodCompletion(this, method)); + } + } + if (m instanceof Field) { + if (prefix == null || THIS.equals(prefix)) { + Field field = (Field) m; + retVal.add(new FieldCompletion(this, field)); + } + } + } + } + String pkg = cu.getPackageName(); + // Completions for superclass methods. + // TODO: Implement me better + if (prefix == null || THIS.equals(prefix)) { + if (td instanceof NormalClassDeclaration) { + NormalClassDeclaration ncd = (NormalClassDeclaration) td; + Type extended = ncd.getExtendedType(); + if (extended != null) { // e.g., not java.lang.Object + String superClassName = extended.toString(); + ClassFile cf = getClassFileFor(cu, superClassName); + if (cf != null) { + addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); + } else { + log.info("[DEBUG]: Couldn't find ClassFile for: " + superClassName); + } + } + } + } + + // Completions for methods of fields, return values of methods, + // static fields/methods, etc. + if (prefix != null && !THIS.equals(prefix)) { + // log.info(prefix); + // log.info(cu); + // log.info(alreadyEntered); + // log.info(td); + // if(prefix) + loadCompletionsForCaretPositionQualified(cu, alreadyEntered, retVal, td, currentMethod, prefix, caret); + } + + } + + private static int countMethodParams(String s) { + int length = s.length(); + if (length == 0) { + return 0; + } + s = s.replace(",", ""); + int lengthAfter = s.length(); + int counttt = length - lengthAfter; + return counttt + 1; + } + + private MethodInfo findMethod(ClassFile cf, String methodName, int paramCount) { + List methodInfoByName = cf.getMethodInfoByName(methodName, paramCount); + if (methodInfoByName == null) { + //log.info("can't find methods in "); + String superClass = cf.getSuperClassName(true); + if(superClass!=null) { + { + ClassFile classEntry = jarManager.getClassEntry(superClass); + if (classEntry != null) { + MethodInfo mi = findMethod(classEntry, methodName, paramCount); + if (mi != null) { + return mi; + } + } + } + } + int count = cf.getImplementedInterfaceCount(); + for (int j = 0; j < count; j++) { + String implementedInterfaceName = cf.getImplementedInterfaceName(j, true); + ClassFile classEntry = jarManager.getClassEntry(superClass); + if (classEntry != null) { + MethodInfo mi = findMethod(classEntry, methodName, paramCount); + if (mi != null) { + return mi; + } + } + } + return null; + } + if (methodInfoByName.size() == 0) { + log.info(methodName); + return null; + } + if (methodInfoByName.size() > 1) { + log.info(methodInfoByName.size() + " " + methodName); + } + + MethodInfo methodInfo = methodInfoByName.get(0); + return methodInfo; + } + + private FieldInfo findField(ClassFile cf, String fieldNameS) { + FieldInfo fieldInfo = cf.getFieldInfoByName(fieldNameS); + if (fieldInfo == null) { + + { + String s = cf.getSuperClassName(true); + if (s != null && s.length() > 1) { + log.info("can't find methods for "); + ClassFile classEntry = jarManager.getClassEntry(s); + if (classEntry != null) { + FieldInfo mi = findField(classEntry, fieldNameS); + if (mi != null) { + return mi; + } + } + } + } + int count = cf.getImplementedInterfaceCount(); + for (int j = 0; j < count; j++) { + String implementedInterfaceName = cf.getImplementedInterfaceName(j, true); + ClassFile classEntry = jarManager.getClassEntry(implementedInterfaceName); + if (classEntry != null) { + FieldInfo mi = findField(classEntry, fieldNameS); + if (mi != null) { + return mi; + } + } + } + return null; + } + return fieldInfo; + } + + private Type resolveTypeWithDot(CompilationUnit cu, String alreadyEntered, + TypeDeclaration td, Method currentMethod, String prefix, int offs, int dot) { + String beforeDot = prefix.substring(0, dot).trim(); + String afterDot = prefix.substring(dot + 1).trim(); + log.info(beforeDot); + log.info(afterDot); + Type type = resolveType2(cu, alreadyEntered, td, currentMethod, beforeDot, offs); + if (type == null) { + log.info("not found " + prefix); + return null; + } + String typeStr = type.getName(true, false); + ClassFile cf = getClassFileFor(cu, typeStr); + if (afterDot.contains("(")) { + int j = afterDot.indexOf("("); + String methodName = afterDot.substring(0, j).trim(); + String params = afterDot.substring(j + 1); + params = params.replace(")", "").trim(); + int countMethodParams = countMethodParams(params); + log.info(methodName + " " + countMethodParams); + { + MethodInfo methodInfo = findMethod(cf, methodName, countMethodParams); + String returnTypeString = methodInfo.getReturnTypeFull(); + returnTypeString = returnTypeString.replaceAll("<.+>", ""); + log.info(returnTypeString + " 123"); + // cf.type + return new Type(returnTypeString); + } + } else if (afterDot.contains("[")) { + log.info("arrays not supported"); + return null; + } else { + FieldInfo fieldInfoByName = findField(cf, afterDot); + if (fieldInfoByName == null) { + log.info("can't fin fields '" + afterDot + "' , " + prefix + " " + cf.getClassName(true)); + return null; + } + String typeString = fieldInfoByName.getTypeString(true); + log.info(typeString); + typeString = typeString.replaceAll("<.+>", ""); + return new Type(typeString); + } + + } + + private void resolveType2WithDot(CompilationUnit cu, String alreadyEntered, TypeDeclaration td, + Method currentMethod, String prefix, int offs, int dot) { + String beforeDot = prefix.substring(0, dot).trim(); + String afterDot = prefix.substring(dot + 1).trim(); + log.info(beforeDot); + log.info(afterDot); + Type type = resolveType2(cu, alreadyEntered, td, currentMethod, beforeDot, offs); + if (type == null) { + log.info("not found " + prefix); + return ; + } + String typeStr = type.getName(true, false); + ClassFile cf = getClassFileFor(cu, typeStr); + if (afterDot.contains("(")) { + int j = afterDot.indexOf("("); + String methodName = afterDot.substring(0, j).trim(); + String params = afterDot.substring(j + 1); + params = params.replace(")", "").trim(); + int countMethodParams = countMethodParams(params); + log.info(methodName + " " + countMethodParams); + { + MethodInfo methodInfo = findMethod(cf, methodName, countMethodParams); + OpenMember.openMember.openMember(methodInfo); + return ; + } + } else if (afterDot.contains("[")) { + log.info("arrays not supported"); + return ; + } else { + FieldInfo fieldInfoByName = findField(cf, afterDot); + if (fieldInfoByName == null) { + log.info("can't find fields '" + afterDot + "' , " + prefix + " " + cf.getClassName(true)); + return ; + } + OpenMember.openMember.openMember(fieldInfoByName);; + } + log.info("not found : " +prefix); + } + + private Type resolveType2(CompilationUnit cu, String alreadyEntered, TypeDeclaration td, + Method currentMethod, String prefix, int offs) { + // String prefix2 = prefix; + int dot = prefix.lastIndexOf("."); + if (dot > -1) { + return resolveTypeWithDot(cu, alreadyEntered, td, currentMethod, prefix, offs, dot); + } + for (Iterator j = td.getMemberIterator(); j.hasNext();) { + Member m = j.next(); + // The prefix might be a field in the local class. + if (m instanceof Field) { + Field field = (Field) m; + if (field.getName().equals(prefix)) { + log.info("found"); + return field.getType(); + } + } + } + if (currentMethod != null) { + for (int i = 0; i < currentMethod.getParameterCount(); i++) { + FormalParameter param = currentMethod.getParameter(i); + String name = param.getName(); + // Assuming prefix is "one level deep" and contains no '.'... + if (prefix.equals(name)) { + return param.getType(); + } + } + } + { + List matchedImports=new ArrayList(); + List imports = cu.getImports(); + List matches = jarManager.getClassesWithUnqualifiedName(prefix, imports); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + String className = cf.getClassName(true); + if(className.endsWith(prefix)) { + matchedImports.add(cf); + } + } + } + if(matchedImports.size()==1) { + log.info("found matched imports "); + return new Type(matchedImports.get(0).getClassName(true)); + } + if(matchedImports.size()>0) { + log.info("found matched imports ,ore than 1"); + }else { + // log.info("found matched imports not found"); + } + } + log.info("not found " + prefix); + return null; + } + + void open(CompilationUnit cu, String alreadyEntered, TypeDeclaration td, + Method currentMethod, String prefix, int offs) { + // String prefix2 = prefix; + int dot = prefix.lastIndexOf("."); + if (dot > -1) { + resolveType2WithDot(cu, alreadyEntered, td, currentMethod, prefix, offs, dot); + return; + } + { + List matchedImports=new ArrayList(); + List imports = cu.getImports(); + List matches = jarManager.getClassesWithUnqualifiedName(prefix, imports); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + String className = cf.getClassName(true); + if(className.endsWith(prefix)) { + matchedImports.add(cf); + } + } + } + if(matchedImports.size()==1) { + log.info("found matched imports "); + OpenMember.openMember.openClass(matchedImports.get(0)); + } + if(matchedImports.size()>0) { + log.info("found matched imports ,ore than 1"); + }else { + // log.info("found matched imports not found"); + } + } + log.info("not found " + prefix); + } + + + /** + * Loads completions for the text at the current caret position, if there is + * a "prefix" of chars and at least one '.' character in the text up to the + * caret. This is currently very limited and needs to be improved. + * + * @param cu + * @param alreadyEntered + * @param retVal + * @param td + * The type declaration the caret is in. + * @param currentMethod + * The method the caret is in, or null if none. + * @param prefix + * The text up to the current caret position. This is guaranteed + * to be non-null not equal to "this". + * @param offs + * The offset of the caret in the document. + */ + private void loadCompletionsForCaretPositionQualified(CompilationUnit cu, String alreadyEntered, + Set retVal, TypeDeclaration td, Method currentMethod, String prefix, int offs) { + + if (EMPTY_STRING.equals(prefix)) { + log.info("string is empty"); + return; + } + String pkg = cu.getPackageName(); + boolean matched = false; + String prefix2 = prefix; + prefix2 = prefix2.replace("\r\n", " "); + prefix2 = prefix2.replace("\n", " "); + prefix2 = prefix2.replace("\t", " "); + prefix2 = prefix2.replace("\r", " "); + // groovy helper + prefix2 = prefix2.replace("@", ""); + Type type = resolveType2(cu, alreadyEntered, td, currentMethod, prefix2, offs); + if (type == null) { + log.info("type is null " + prefix); + } else { + if (type.isArray()) { + ClassFile cf = getClassFileFor(cu, "java.lang.Object"); + addCompletionsForExtendedClass(retVal, cu, cf, pkg, null); + FieldCompletion fc = FieldCompletion.createLengthCompletion(this, type); + retVal.add(fc); + } else if (!type.isBasicType()) { + String typeStr = type.getName(true, false); + ClassFile cf = getClassFileFor(cu, typeStr); + // Add completions for extended class type chain + if (cf != null) { + Map typeParamMap = createTypeParamMap(type, cf); + addCompletionsForExtendedClass(retVal, cu, cf, pkg, typeParamMap); + // Add completions for all implemented interfaces + // TODO: Only do this if type is abstract! + for (int i = 0; i < cf.getImplementedInterfaceCount(); i++) { + String inter = cf.getImplementedInterfaceName(i, true); + cf = getClassFileFor(cu, inter); + log.info(cf); + } + } + } + matched = true; + } + + // The prefix might be for a local variable in the current method. + if (currentMethod != null) { + + boolean found = false; + + // Check parameters to the current method + for (int i = 0; i < currentMethod.getParameterCount(); i++) { + FormalParameter param = currentMethod.getParameter(i); + String name = param.getName(); + // Assuming prefix is "one level deep" and contains no '.'... + if (prefix.equals(name)) { + addCompletionsForLocalVarsMethods(cu, param, retVal); + found = true; + break; + } + } + + // If a formal param wasn't matched, check local variables. + if (!found) { + CodeBlock body = currentMethod.getBody(); + if (body != null) { + loadCompletionsForCaretPositionQualifiedCodeBlock(cu, retVal, td, body, prefix, offs); + } + } + + matched |= found; + + } + + // Could be a class name, in which case we'll need to add completions + // for static fields and methods. + if (!matched) { + List imports = cu.getImports(); + List matches = jarManager.getClassesWithUnqualifiedName(prefix, imports); + if (matches != null) { + for (int i = 0; i < matches.size(); i++) { + ClassFile cf = matches.get(i); + addCompletionsForStaticMembers(retVal, cu, cf, pkg); + } + } + } + + } + + private void loadCompletionsForCaretPositionQualifiedCodeBlock(CompilationUnit cu, Set retVal, + TypeDeclaration td, CodeBlock block, String prefix, int offs) { + + boolean found = false; + + for (int i = 0; i < block.getLocalVarCount(); i++) { + LocalVariable var = block.getLocalVar(i); + if (var.getNameEndOffset() <= offs) { + // TODO: This assumes prefix is "1 level deep" + if (prefix.equals(var.getName())) { + addCompletionsForLocalVarsMethods(cu, var, retVal); + found = true; + break; + } + } else { // This and all following declared after offs + break; + } + } + + if (found) { + return; + } + + for (int i = 0; i < block.getChildBlockCount(); i++) { + CodeBlock child = block.getChildBlock(i); + if (child.containsOffset(offs)) { + loadCompletionsForCaretPositionQualifiedCodeBlock(cu, retVal, td, child, prefix, offs); + break; // All other blocks are past this one + } + // If we've reached a block that's past the offset we're + // searching for... + else if (child.getNameStartOffset() > offs) { + break; + } + } + + } + + /** + * Loads completions for a single import statement. + * + * @param importStr + * The import statement. + * @param pkgName + * The package of the source currently being parsed. + */ + private void loadCompletionsForImport(Set set, String importStr, String pkgName) { + + if (importStr.endsWith(".*")) { + String pkg = importStr.substring(0, importStr.length() - 2); + boolean inPkg = pkg.equals(pkgName); + List classes = jarManager.getClassesInPackage(pkg, inPkg); + for (ClassFile cf : classes) { + set.add(new ClassCompletion(this, cf)); + } + } + + else { + ClassFile cf = jarManager.getClassEntry(importStr); + if (cf != null) { + set.add(new ClassCompletion(this, cf)); + } + } + + } + + /** + * Loads completions for all import statements. + * + * @param cu + * The compilation unit being parsed. + */ + private void loadImportCompletions(Set set, String text, CompilationUnit cu) { + + // Fully-qualified completions are handled elsewhere, so no need to + // duplicate the work here + if (text.indexOf('.') > -1) { + return; + } + + // long startTime = System.currentTimeMillis(); + + String pkgName = cu.getPackageName(); + loadCompletionsForImport(set, JAVA_LANG_PACKAGE, pkgName); + for (Iterator i = cu.getImportIterator(); i.hasNext();) { + ImportDeclaration id = i.next(); + String name = id.getName(); + if (!JAVA_LANG_PACKAGE.equals(name)) { + loadCompletionsForImport(set, name, pkgName); + } + } + // Collections.sort(completions); + + // long time = System.currentTimeMillis() - startTime; + // log.info("imports loaded in: " + time); + + } + + /** + * Removes a jar from the "build path." + * + * @param jar + * The jar to remove. + * @return Whether the jar was removed. This will be false if + * the jar was not on the build path. + * @see #addJar(LibraryInfo) + * @see #getJars() + * @see #clearJars() + */ + public boolean removeJar(File jar) { + boolean removed = jarManager.removeClassFileSource(jar); + // The memory used by the completions can be quite large, so go ahead + // and clear out the completions list so no-longer-needed ones are + // eligible for GC. + if (removed) { + clear(); + } + return removed; + } + + /** + * Sets the parent Java provider. + * + * @param javaProvider + * The parent completion provider. + */ + void setJavaProvider(JavaCompletionProvider javaProvider) { + this.javaProvider = javaProvider; + } + } \ No newline at end of file diff --git a/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java b/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java index 2e94841e..3fd92ff6 100644 --- a/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java +++ b/src/main/java/org/fife/rsta/ac/java/classreader/MethodInfo.java @@ -62,7 +62,7 @@ public class MethodInfo extends MemberInfo implements AccessFlags { * The type of all parameters to this method. Note that this cache will * be short-lived, as classes that take type parameters will pass their * type arguments down to individual MethodInfos when doing - * completions, to ensure types are as correct as possible. + * completions, to ensure types are as correct as possible. */ private String[] paramTypes; @@ -71,6 +71,7 @@ public class MethodInfo extends MemberInfo implements AccessFlags { */ private String returnType; + private String returnTypeQualified; /** * Cached string representing the name and parameters for this method. */ @@ -118,7 +119,7 @@ private void appendParamDescriptors(StringBuilder sb) { sb.append(", "); } } - + } @@ -129,7 +130,7 @@ private void appendParamDescriptors(StringBuilder sb) { * lazily recomputed the next time they are needed. This allows this * MethodInfo to be used for code completion for instances * of the same class initialized with different type arguments.

    - * + * * Note that if this method does not have parameterized arguments or * return type, calling this method won't affect its behavior. */ @@ -448,11 +449,20 @@ public String getReturnTypeString(boolean fullyQualified) { if(!fullyQualified) { if(returnType != null && returnType.indexOf(".") > -1) { - return returnType.substring(returnType.lastIndexOf(".") +1, returnType.length()); + return returnType.substring(returnType.lastIndexOf(".") +1, returnType.length()); } } return returnType; } + public String getReturnTypeFull() { + if (returnTypeQualified==null) { + returnTypeQualified = getReturnTypeStringFromTypeSignature(true); + if (returnTypeQualified==null) { + returnTypeQualified = getReturnTypeStringFromDescriptor(true); + } + } + return returnTypeQualified; + } /** diff --git a/src/main/java/org/fife/rsta/ac/java/classreader/constantpool/ConstantUtf8Info.java b/src/main/java/org/fife/rsta/ac/java/classreader/constantpool/ConstantUtf8Info.java index 6c319ef0..2799b84e 100644 --- a/src/main/java/org/fife/rsta/ac/java/classreader/constantpool/ConstantUtf8Info.java +++ b/src/main/java/org/fife/rsta/ac/java/classreader/constantpool/ConstantUtf8Info.java @@ -97,7 +97,8 @@ else if (15==(b>>4)) { representedString = new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException uee) { // Never happens. uee.printStackTrace(); - System.exit(0); + // System.exit(0); + throw new Error(uee); } return representedString; diff --git a/src/main/resources/examples/JavaExample.txt b/src/main/resources/examples/JavaExample.txt index 57adb381..b45d1f36 100644 --- a/src/main/resources/examples/JavaExample.txt +++ b/src/main/resources/examples/JavaExample.txt @@ -1,12 +1,22 @@ +import javax.swing.JComponent; +import java.util.Arrays; + public class ExampleCode { private String strField; protected int aaa; protected int abb; public float foo; + java.util.ArrayList al; + JComponent jc; public ExampleCode(int value) { this.value = value; + Object aaa = jc.actionMap; + jc.getColorModel(); + + Object yy = Arrays.clone(); + yy. } public String getX() { @@ -27,31 +37,31 @@ public class ExampleCode { /** Ctrl+click my stuff! */ private class Inner1 { - + private int aaa; private double d; private String strField; - + public void doSomething(double newVal) { d = newVal; aaa = abb = clickable; strField = getX(); // getX() clickable } - + } - + /** Ctrl+click my stuff also! */ private static class Inner2 { - + private double d; private String strField; - + public void doSomething(double newVal) { d = newVal; aaa = abb = notClickable; strField = getX(); // getX() not clickable } - + } - -} \ No newline at end of file + +} \ No newline at end of file