diff --git a/pom.xml b/pom.xml index fafddc47..49b0276e 100644 --- a/pom.xml +++ b/pom.xml @@ -127,6 +127,7 @@ 1.8 + 0.9.10 @@ -210,6 +211,11 @@ jhotdraw 7.6.0 + + org.reflections + reflections + ${reflections.version} + diff --git a/src/main/java/net/imagej/ui/swing/script/TextEditor.java b/src/main/java/net/imagej/ui/swing/script/TextEditor.java index db3a4d62..430a8b11 100644 --- a/src/main/java/net/imagej/ui/swing/script/TextEditor.java +++ b/src/main/java/net/imagej/ui/swing/script/TextEditor.java @@ -59,6 +59,7 @@ import java.io.Writer; import java.net.URL; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -107,6 +108,10 @@ import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; import org.scijava.Context; import org.scijava.command.CommandService; import org.scijava.event.ContextDisposingEvent; @@ -168,6 +173,7 @@ public class TextEditor extends JFrame implements ActionListener, } private static AbstractTokenMakerFactory tokenMakerFactory = null; + private static Reflections reflections = null; private JTabbedPane tabbed; private JMenuItem newFile, open, save, saveas, compileAndRun, compile, @@ -2064,9 +2070,41 @@ public String getSelectedTextOrAsk(final String label) { public String getSelectedClassNameOrAsk() { String className = getSelectedTextOrAsk("Class name"); if (className != null) className = className.trim(); + return className; } + /** + * Returns the static Reflections instance, constructing it + * if it doesn't already exist. This is to limit the number of + * classpath scans. + * + * @return static {@link Reflections} instance + */ + private static Reflections getReflections() { + //TODO consider moving this to a service with plugins to determine how the + //packages are filtered. For example having scijava-common on the classpath + //adds org.scijava. Adding imagej-common would add net.imagej to the filter + //list.. etc... + if (reflections == null) { + synchronized(TextEditor.class) { + if (reflections == null) { + final Collection packages = new HashSet<>(); + packages.addAll(ClasspathHelper.forPackage("net.imagej")); + packages.addAll(ClasspathHelper.forPackage("org.scijava")); + packages.addAll(ClasspathHelper.forPackage("net.imglib2")); + packages.addAll(ClasspathHelper.forPackage("io.scif")); + packages.addAll(ClasspathHelper.forPackage("sc.fiji")); + packages.addAll(ClasspathHelper.forPackage("ij")); + reflections = new Reflections(new ConfigurationBuilder().setUrls( + packages).setScanners(new SubTypesScanner(false))); + } + } + } + + return reflections; + } + private static void append(final JTextArea textArea, final String text) { final int length = textArea.getDocument().getLength(); textArea.insert(text, length); @@ -2209,7 +2247,38 @@ private void updateGitDirectory() { public void addImport(final String className) { if (className != null) { - new TokenFunctions(getTextArea()).addImport(className.trim()); + + boolean addRaw = true; + + // If there are NO package separators then this is a raw class name. Try + // and find matching packages + + // NB: decided to only look for complete class names without packages. + // Matching "endsWith(className) can produce a plethora of false positives + // which we want to limit, because the current implementation imports + // all matches. + // Some alternatives to consider (including combinations): + // - match *className + // - match *className* + // - match .className* + // - if >1 match show list to the user to choose a "winner" to import + if (!className.contains(".")) { + final String packagedClass = "." + className; + final Reflections refl = TextEditor.getReflections(); + final StringBuilder sb = new StringBuilder(); + + for (final String type : refl.getAllTypes()) { + // look for "blah.className" + if (type.endsWith(packagedClass)) { + addRaw = false; + new TokenFunctions(getTextArea()).addImport(type.trim()); + } + } + } + + // If there was a package separator or no matching packages, import the raw + // class. + if (addRaw) new TokenFunctions(getTextArea()).addImport(className.trim()); } }