From 7507734c711156b8428ba4c23f06f157301f449c Mon Sep 17 00:00:00 2001 From: Albert Cardona Date: Thu, 12 Nov 2020 21:31:09 +0000 Subject: [PATCH 1/7] Prevent Script Editor window from being larger than the desktop. This can happen when using a networked account across different computers or with different monitors. --- src/main/java/org/scijava/ui/swing/script/TextEditor.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/ui/swing/script/TextEditor.java b/src/main/java/org/scijava/ui/swing/script/TextEditor.java index 07323382..a2299315 100644 --- a/src/main/java/org/scijava/ui/swing/script/TextEditor.java +++ b/src/main/java/org/scijava/ui/swing/script/TextEditor.java @@ -988,7 +988,12 @@ public void loadPreferences() { final int windowWidth = prefService.getInt(getClass(), WINDOW_WIDTH, dim.width); final int windowHeight = prefService.getInt(getClass(), WINDOW_HEIGHT, dim.height); - setPreferredSize(new Dimension(windowWidth, windowHeight)); + // Avoid creating a window larger than the desktop + final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + if (windowWidth > screen.getWidth() || windowHeight > screen.getHeight()) + setPreferredSize(new Dimension(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)); + else + setPreferredSize(new Dimension(windowWidth, windowHeight)); final int mainDivLocation = prefService.getInt(getClass(), MAIN_DIV_LOCATION, body.getDividerLocation()); body.setDividerLocation(mainDivLocation); From 0a1b4436e0d1d19cc3460973940cc17da18198a8 Mon Sep 17 00:00:00 2001 From: Albert Cardona Date: Fri, 13 Nov 2020 23:36:04 +0000 Subject: [PATCH 2/7] ClassUtil: inspect all jar files in search of all class names, and collect info and URLs from their pom.xml --- .../scijava/ui/swing/script/ClassUtil.java | 233 ++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/main/java/org/scijava/ui/swing/script/ClassUtil.java diff --git a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java new file mode 100644 index 00000000..2c6ba062 --- /dev/null +++ b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java @@ -0,0 +1,233 @@ +package org.scijava.ui.swing.script; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Scanner; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ClassUtil { + + static private final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash + + /** Cache of class names vs list of URLs found in the pom.xml files of their contaning jar files, if any. */ + static private final Map class_urls = new HashMap<>(); + + /** Cache of subURL javadoc at https://javadoc.scijava.org */ + static private final HashMap scijava_javadoc_URLs = new HashMap<>(); + + static private final void ensureCache() { + synchronized (class_urls) { + if (class_urls.isEmpty()) + class_urls.putAll(findAllClasses()); + } + } + + static public final void ensureSciJavaSubURLCache() { + synchronized (scijava_javadoc_URLs) { + if (!scijava_javadoc_URLs.isEmpty()) return; + Scanner scanner = null; + try { + final Pattern pattern = Pattern.compile("
"); + final URLConnection connection = new URL(scijava_javadoc_URL).openConnection(); + scanner = new Scanner(connection.getInputStream()); + while (scanner.hasNext()) { + final Matcher matcher = pattern.matcher(scanner.nextLine()); + if (matcher.find()) { + String name = matcher.group(1).toLowerCase(); + if (name.endsWith("/")) name = name.substring(0, name.length() -1); + scijava_javadoc_URLs.put(name, scijava_javadoc_URL + matcher.group(1)); + } + } + scanner.close(); + } catch ( Exception e ) { + e.printStackTrace(); + } finally { + if (null != scanner) scanner.close(); + } + } + } + + static public HashMap findClassDocumentationURLs(final String s) { + ensureCache(); + final HashMap matches = new HashMap<>(); + for (final Map.Entry entry: class_urls.entrySet()) { + if (entry.getKey().contains(s)) { + final JarProperties props = entry.getValue(); + matches.put(entry.getKey(), new JarProperties(props.name, new ArrayList(props.urls))); + } + } + return matches; + } + + static public HashMap> findDocumentationForClass(final String s) { + final HashMap matches = findClassDocumentationURLs(s); + ensureSciJavaSubURLCache(); + + final Pattern java8 = Pattern.compile("^(java|javax|org.omg|org.w3c|org.xml|org.ietf.jgss)\\..*$"); + + final HashMap> class_urls = new HashMap<>(); + + for (final Map.Entry entry: matches.entrySet()) { + final String classname = entry.getKey(); + final ArrayList urls = new ArrayList<>(); + class_urls.put(classname, urls); + if (java8.matcher(classname).matches()) { + urls.add(scijava_javadoc_URLs.get("java8") + classname.replace('.', '/') + ".html"); + } else { + final JarProperties props = entry.getValue(); + // Find the first URL with git in it + for (final String url : props.urls) { + final boolean github = url.contains("/github.com"), + gitlab = url.contains("/gitlab.com"); + if (github || gitlab) { + // Find the 5th slash, e.g. https://github.com/imglib/imglib2/ + int count = 0; + int last = 0; + while (count < 5) { + last = url.indexOf('/', last + 1); + if (-1 == last) break; // less than 5 found + ++count; + } + String urlbase = url; + if (5 == count) urlbase = url.substring(0, last); // without the ending slash + // Assume maven, since these URLs were found in a pom.xml: src/main/java/ + urls.add(urlbase + (gitlab ? "/-" : "") + "/blob/master/src/main/java/" + classname.replace('.', '/') + ".java"); + break; + } + } + // Try to find a javadoc in the scijava website + if (null != props.name) { + String scijava_javadoc_url = scijava_javadoc_URLs.get(props.name.toLowerCase()); + if (null == scijava_javadoc_url) { + // Try cropping name at the first whitespace if any (e.g. "ImgLib2 Core Library" to "ImgLib2") + final int ispace = props.name.indexOf(' '); + if (-1 != ispace) { + scijava_javadoc_url = scijava_javadoc_URLs.get(props.name.toLowerCase().substring(0, ispace)); + } + } + if (null != scijava_javadoc_url) { + urls.add(scijava_javadoc_url + classname.replace('.', '/') + ".html"); + } else { + // Try Fiji: could be a plugin + Scanner scanner = null; + try { + final String url = scijava_javadoc_URL + "Fiji/" + classname.replace('.', '/') + ".html"; + final URLConnection c = new URL(url).openConnection(); + scanner = new Scanner(c.getInputStream()); + while (scanner.hasNext()) { + final String line = scanner.nextLine(); + if (line.contains("")) { + if (!line.contains("<title>404")) { + urls.add(url); + } + break; + } + } + } catch (Exception e) { + // Ignore: 404 that wasn't redirected to an error page + } finally { + if (null != scanner) scanner.close(); + } + } + } + } + } + + return class_urls; + } + + static public final class JarProperties { + public final ArrayList<String> urls; + public String name = null; + public JarProperties(final String name, final ArrayList<String> urls) { + this.name = name; + this.urls = urls; + } + } + + static public final HashMap<String, JarProperties> findAllClasses() { + // Find all jar files + final ArrayList<String> jarFilePaths = new ArrayList<String>(); + final LinkedList<String> dirs = new LinkedList<>(); + dirs.add(System.getProperty("java.home")); + dirs.add(System.getProperty("ij.dir")); + final HashSet<String> seenDirs = new HashSet<>(); + while (!dirs.isEmpty()) { + final String filepath = dirs.removeFirst(); + final File file = new File(filepath); + seenDirs.add(file.getAbsolutePath()); + if (file.exists()) { + if (file.isDirectory()) { + for (final File child : file.listFiles()) { + final String childfilepath = child.getAbsolutePath(); + if (seenDirs.contains(childfilepath)) continue; + if (child.isDirectory()) dirs.add(childfilepath); + else if (childfilepath.endsWith(".jar")) jarFilePaths.add(childfilepath); + } + } + } + } + // Find all classes from all jar files + final HashMap<String, JarProperties> class_urls = new HashMap<>(); + final Pattern urlpattern = Pattern.compile(">(http.*?)<"); + final Pattern namepattern = Pattern.compile("<name>(.*?)<"); + for (final String jarpath : jarFilePaths) { + JarFile jar = null; + try { + jar = new JarFile(jarpath); + final Enumeration<JarEntry> entries = jar.entries(); + final ArrayList<String> urls = new ArrayList<>(); + final JarProperties props = new JarProperties(null, urls); + // For every filepath in the jar zip archive + while (entries.hasMoreElements()) { + final JarEntry entry = entries.nextElement(); + if (entry.isDirectory()) continue; + if (entry.getName().endsWith(".class")) { + String classname = entry.getName().replace('/', '.'); + final int idollar = classname.indexOf('$'); + if (-1 != idollar) { + classname = classname.substring(0, idollar); // truncate at the first dollar sign + } else { + classname = classname.substring(0, classname.length() - 6); // without .class + } + class_urls.put(classname, props); + } else if (entry.getName().endsWith("/pom.xml")) { + final Scanner scanner = new Scanner(jar.getInputStream(entry)); + while (scanner.hasNext()) { + final String line = scanner.nextLine(); + final Matcher matcher1 = urlpattern.matcher(line); + if (matcher1.find()) { + urls.add(matcher1.group(1)); + } + if (null == props.name) { + final Matcher matcher2 = namepattern.matcher(line); + if (matcher2.find()) { + props.name = matcher2.group(1); + } + } + } + scanner.close(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (null != jar) try { + jar.close(); + } catch (IOException e) { e.printStackTrace(); } + } + } + return class_urls; + } +} From 80fd9dd7369fa3dce348527675bb7ab8a1fb4e96 Mon Sep 17 00:00:00 2001 From: Albert Cardona <sapristi@gmail.com> Date: Sat, 14 Nov 2020 01:39:36 +0000 Subject: [PATCH 3/7] Create new menu item "Source or javadoc for class or package..." which fixes or replaces the "Open Help" menu item, by using ClassUtils and showing a UI that lists all matching classes with buttons to open their source code in github or gitlab and the javadoc when available (will guess the URL, load it, and not show a link to it when 404). Can match partial text (the list of possible classes may be longer) and can also match package names or partial package names. Any substring of a fully qualified class name will be matched, which is fabulous. (Compare with current implementatio nof "Open Help..." which requires a fully qualified class name to work, which is not useful as often one does not know it or not have it handy in a script.) --- .../scijava/ui/swing/script/TextEditor.java | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/ui/swing/script/TextEditor.java b/src/main/java/org/scijava/ui/swing/script/TextEditor.java index a2299315..671f62fa 100644 --- a/src/main/java/org/scijava/ui/swing/script/TextEditor.java +++ b/src/main/java/org/scijava/ui/swing/script/TextEditor.java @@ -30,6 +30,7 @@ package org.scijava.ui.swing.script; import java.awt.Color; +import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; @@ -108,6 +109,7 @@ import javax.swing.JCheckBoxMenuItem; import javax.swing.JFileChooser; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; @@ -214,7 +216,7 @@ public class TextEditor extends JFrame implements ActionListener, openMacroFunctions, decreaseFontSize, increaseFontSize, chooseFontSize, chooseTabSize, gitGrep, openInGitweb, replaceTabsWithSpaces, replaceSpacesWithTabs, toggleWhiteSpaceLabeling, zapGremlins, - savePreferences, toggleAutoCompletionMenu; + savePreferences, toggleAutoCompletionMenu, openClassOrPackageHelp; private RecentFilesMenuItem openRecent; private JMenu gitMenu, tabsMenu, fontSizeMenu, tabSizeMenu, toolsMenu, runMenu, whiteSpaceMenu; @@ -483,6 +485,8 @@ public TextEditor(final Context context) { openHelp = addToMenu(toolsMenu, "Open Help for Class (with frames)...", 0, 0); openHelp.setMnemonic(KeyEvent.VK_P); + openClassOrPackageHelp = addToMenu(toolsMenu, "Source or javadoc for class or package...", 0, 0); + openClassOrPackageHelp.setMnemonic(KeyEvent.VK_S); openMacroFunctions = addToMenu(toolsMenu, "Open Help on Macro Functions...", 0, 0); openMacroFunctions.setMnemonic(KeyEvent.VK_H); @@ -1400,6 +1404,7 @@ else if (source == savePreferences) { } else if (source == openHelp) openHelp(null); else if (source == openHelpWithoutFrames) openHelp(null, false); + else if (source == openClassOrPackageHelp) openClassOrPackageHelp(null); else if (source == openMacroFunctions) try { new MacroFunctions(this).openHelp(getTextArea().getSelectedText()); } @@ -2664,6 +2669,79 @@ public void openHelp(String className, final boolean withFrames) { handleException(e); } } + + /** + * @param text Either a classname, or a partial class name, or package name or any part of the fully qualified class name. + */ + public void openClassOrPackageHelp(String text) { + if (text == null) + text = getSelectedClassNameOrAsk(); + if (null == text) return; + new Thread(new FindClassSourceAndJavadoc(text)).start(); // fork away from event dispatch thread + } + + public class FindClassSourceAndJavadoc implements Runnable { + private final String text; + public FindClassSourceAndJavadoc(final String text) { + this.text = text; + } + @Override + public void run() { + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + final HashMap<String, ArrayList<String>> matches; + try { + matches = ClassUtil.findDocumentationForClass(text); + } finally { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + if (matches.isEmpty()) { + JOptionPane.showMessageDialog(getEditorPane(), "No info found for:\n'" + text +'"'); + return; + } + final JPanel panel = new JPanel(); + final GridBagLayout gridbag = new GridBagLayout(); + final GridBagConstraints c = new GridBagConstraints(); + panel.setLayout(gridbag); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + final List<String> keys = new ArrayList<String>(matches.keySet()); + Collections.sort(keys); + c.gridy = 0; + for (final String classname: keys) { + c.gridx = 0; + c.anchor = GridBagConstraints.EAST; + final JLabel class_label = new JLabel(classname); + gridbag.setConstraints(class_label, c); + panel.add(class_label); + for (final String url: matches.get(classname)) { + c.gridx += 1; + c.anchor = GridBagConstraints.WEST; + final JButton link = new JButton(url.endsWith("java") ? "Source" : "JavaDoc"); + gridbag.setConstraints(link, c); + panel.add(link); + link.addActionListener(new ActionListener() { + public void actionPerformed(final ActionEvent event) { + try { + platformService.open(new URL(url)); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + c.gridy += 1; + } + final JScrollPane jsp = new JScrollPane(panel); + //jsp.setPreferredSize(new Dimension(800, 500)); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + final JFrame frame = new JFrame(text); + frame.getContentPane().add(jsp); + frame.pack(); + frame.setVisible(true); + } + }); + } + } public void extractSourceJar() { final File file = openWithDialog(null); From 5cf2a7af418905cbf176f9d16a30507940b556e1 Mon Sep 17 00:00:00 2001 From: Albert Cardona <sapristi@gmail.com> Date: Sat, 14 Nov 2020 10:36:52 +0000 Subject: [PATCH 4/7] Ensure the correct java version is used, although the list of package suffixes comes from java 8. --- .../java/org/scijava/ui/swing/script/ClassUtil.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java index 2c6ba062..f123aa0d 100644 --- a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java @@ -74,16 +74,21 @@ static public HashMap<String, ArrayList<String>> findDocumentationForClass(final final HashMap<String, JarProperties> matches = findClassDocumentationURLs(s); ensureSciJavaSubURLCache(); - final Pattern java8 = Pattern.compile("^(java|javax|org.omg|org.w3c|org.xml|org.ietf.jgss)\\..*$"); - + final Pattern javaPackages = Pattern.compile("^(java|javax|org\\.omg|org\\.w3c|org\\.xml|org\\.ietf\\.jgss)\\..*$"); + final String version = System.getProperty("java.version"); + final String majorVersion = version.startsWith("1.") ? + version.substring(2, version.indexOf('.', 2)) + : version.substring(0, version.indexOf('.')); + final String javaDoc = "java" + majorVersion; + final HashMap<String, ArrayList<String>> class_urls = new HashMap<>(); for (final Map.Entry<String, JarProperties> entry: matches.entrySet()) { final String classname = entry.getKey(); final ArrayList<String> urls = new ArrayList<>(); class_urls.put(classname, urls); - if (java8.matcher(classname).matches()) { - urls.add(scijava_javadoc_URLs.get("java8") + classname.replace('.', '/') + ".html"); + if (javaPackages.matcher(classname).matches()) { + urls.add(scijava_javadoc_URLs.get(javaDoc) + classname.replace('.', '/') + ".html"); } else { final JarProperties props = entry.getValue(); // Find the first URL with git in it From 4583ccfecf7c3002f85c14966efcf1b4de8f9cac Mon Sep 17 00:00:00 2001 From: Albert Cardona <sapristi@gmail.com> Date: Sat, 14 Nov 2020 16:25:20 +0000 Subject: [PATCH 5/7] ClassUtil: generalize static method findAllClasses so that it an be used for other directories. --- .../scijava/ui/swing/script/ClassUtil.java | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java index f123aa0d..3c2f5e0f 100644 --- a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.jar.JarEntry; @@ -28,8 +29,12 @@ public class ClassUtil { static private final void ensureCache() { synchronized (class_urls) { - if (class_urls.isEmpty()) - class_urls.putAll(findAllClasses()); + if (class_urls.isEmpty()) { + final ArrayList<String> dirs = new ArrayList<>(); + dirs.add(System.getProperty("java.home")); + dirs.add(System.getProperty("ij.dir")); + class_urls.putAll(findAllClasses(dirs)); + } } } @@ -161,25 +166,22 @@ public JarProperties(final String name, final ArrayList<String> urls) { } } - static public final HashMap<String, JarProperties> findAllClasses() { + static public final HashMap<String, JarProperties> findAllClasses(final List<String> jar_folders) { // Find all jar files final ArrayList<String> jarFilePaths = new ArrayList<String>(); - final LinkedList<String> dirs = new LinkedList<>(); - dirs.add(System.getProperty("java.home")); - dirs.add(System.getProperty("ij.dir")); + final LinkedList<String> dirs = new LinkedList<>(jar_folders); final HashSet<String> seenDirs = new HashSet<>(); while (!dirs.isEmpty()) { final String filepath = dirs.removeFirst(); + if (null == filepath) continue; final File file = new File(filepath); seenDirs.add(file.getAbsolutePath()); - if (file.exists()) { - if (file.isDirectory()) { - for (final File child : file.listFiles()) { - final String childfilepath = child.getAbsolutePath(); - if (seenDirs.contains(childfilepath)) continue; - if (child.isDirectory()) dirs.add(childfilepath); - else if (childfilepath.endsWith(".jar")) jarFilePaths.add(childfilepath); - } + if (file.exists() && file.isDirectory()) { + for (final File child : file.listFiles()) { + final String childfilepath = child.getAbsolutePath(); + if (seenDirs.contains(childfilepath)) continue; + if (child.isDirectory()) dirs.add(childfilepath); + else if (childfilepath.endsWith(".jar")) jarFilePaths.add(childfilepath); } } } From 3020432a9ca54bc894a981a9ea5e5e6fb222e21f Mon Sep 17 00:00:00 2001 From: Albert Cardona <sapristi@gmail.com> Date: Sat, 14 Nov 2020 16:34:23 +0000 Subject: [PATCH 6/7] If a classname doesn't have any URLS associated with it, then search in DuckDuckGo. --- .../java/org/scijava/ui/swing/script/TextEditor.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/TextEditor.java b/src/main/java/org/scijava/ui/swing/script/TextEditor.java index 671f62fa..57423696 100644 --- a/src/main/java/org/scijava/ui/swing/script/TextEditor.java +++ b/src/main/java/org/scijava/ui/swing/script/TextEditor.java @@ -2712,10 +2712,18 @@ public void run() { final JLabel class_label = new JLabel(classname); gridbag.setConstraints(class_label, c); panel.add(class_label); - for (final String url: matches.get(classname)) { + ArrayList<String> urls = matches.get(classname); + if (urls.isEmpty()) { + urls = new ArrayList<String>(); + urls.add("https://duckduckgo.com/?q=" + classname); + } + for (final String url: urls) { c.gridx += 1; c.anchor = GridBagConstraints.WEST; - final JButton link = new JButton(url.endsWith("java") ? "Source" : "JavaDoc"); + String title = "JavaDoc"; + if (url.endsWith(".java")) title = "Source"; + else if (url.contains("duckduckgo")) title = "Search..."; + final JButton link = new JButton(title); gridbag.setConstraints(link, c); panel.add(link); link.addActionListener(new ActionListener() { From d8b7d3277852e43d5d71eb6aa2d9e6a39af62a53 Mon Sep 17 00:00:00 2001 From: Albert Cardona <sapristi@gmail.com> Date: Mon, 16 Nov 2020 09:41:06 +0000 Subject: [PATCH 7/7] ClassUtil: search among the multiple words in the repository name for a name that matches a subURL of javadoc.scijava.org --- src/main/java/org/scijava/ui/swing/script/ClassUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java index 3c2f5e0f..03dfe576 100644 --- a/src/main/java/org/scijava/ui/swing/script/ClassUtil.java +++ b/src/main/java/org/scijava/ui/swing/script/ClassUtil.java @@ -121,9 +121,9 @@ static public HashMap<String, ArrayList<String>> findDocumentationForClass(final String scijava_javadoc_url = scijava_javadoc_URLs.get(props.name.toLowerCase()); if (null == scijava_javadoc_url) { // Try cropping name at the first whitespace if any (e.g. "ImgLib2 Core Library" to "ImgLib2") - final int ispace = props.name.indexOf(' '); - if (-1 != ispace) { - scijava_javadoc_url = scijava_javadoc_URLs.get(props.name.toLowerCase().substring(0, ispace)); + for (final String word: props.name.split(" ")) { + scijava_javadoc_url = scijava_javadoc_URLs.get(word.toLowerCase()); + if (null != scijava_javadoc_url) break; // found a valid one } } if (null != scijava_javadoc_url) {