|
| 1 | +package org.scijava.ui.swing.script; |
| 2 | + |
| 3 | +import java.io.File; |
| 4 | +import java.io.IOException; |
| 5 | +import java.net.URL; |
| 6 | +import java.net.URLConnection; |
| 7 | +import java.util.ArrayList; |
| 8 | +import java.util.Enumeration; |
| 9 | +import java.util.HashMap; |
| 10 | +import java.util.HashSet; |
| 11 | +import java.util.LinkedList; |
| 12 | +import java.util.Map; |
| 13 | +import java.util.Scanner; |
| 14 | +import java.util.jar.JarEntry; |
| 15 | +import java.util.jar.JarFile; |
| 16 | +import java.util.regex.Matcher; |
| 17 | +import java.util.regex.Pattern; |
| 18 | + |
| 19 | +public class ClassUtil { |
| 20 | + |
| 21 | + static private final String scijava_javadoc_URL = "https://javadoc.scijava.org/"; // with ending slash |
| 22 | + |
| 23 | + /** Cache of class names vs list of URLs found in the pom.xml files of their contaning jar files, if any. */ |
| 24 | + static private final Map<String, JarProperties> class_urls = new HashMap<>(); |
| 25 | + |
| 26 | + /** Cache of subURL javadoc at https://javadoc.scijava.org */ |
| 27 | + static private final HashMap<String, String> scijava_javadoc_URLs = new HashMap<>(); |
| 28 | + |
| 29 | + static private final void ensureCache() { |
| 30 | + synchronized (class_urls) { |
| 31 | + if (class_urls.isEmpty()) |
| 32 | + class_urls.putAll(findAllClasses()); |
| 33 | + } |
| 34 | + } |
| 35 | + |
| 36 | + static public final void ensureSciJavaSubURLCache() { |
| 37 | + synchronized (scijava_javadoc_URLs) { |
| 38 | + if (!scijava_javadoc_URLs.isEmpty()) return; |
| 39 | + Scanner scanner = null; |
| 40 | + try { |
| 41 | + final Pattern pattern = Pattern.compile("<div class=\"jdbox\"><div><a href=\"(.*?)\">"); |
| 42 | + final URLConnection connection = new URL(scijava_javadoc_URL).openConnection(); |
| 43 | + scanner = new Scanner(connection.getInputStream()); |
| 44 | + while (scanner.hasNext()) { |
| 45 | + final Matcher matcher = pattern.matcher(scanner.nextLine()); |
| 46 | + if (matcher.find()) { |
| 47 | + String name = matcher.group(1).toLowerCase(); |
| 48 | + if (name.endsWith("/")) name = name.substring(0, name.length() -1); |
| 49 | + scijava_javadoc_URLs.put(name, scijava_javadoc_URL + matcher.group(1)); |
| 50 | + } |
| 51 | + } |
| 52 | + scanner.close(); |
| 53 | + } catch ( Exception e ) { |
| 54 | + e.printStackTrace(); |
| 55 | + } finally { |
| 56 | + if (null != scanner) scanner.close(); |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + static public HashMap<String, JarProperties> findClassDocumentationURLs(final String s) { |
| 62 | + ensureCache(); |
| 63 | + final HashMap<String, JarProperties> matches = new HashMap<>(); |
| 64 | + for (final Map.Entry<String, JarProperties> entry: class_urls.entrySet()) { |
| 65 | + if (entry.getKey().contains(s)) { |
| 66 | + final JarProperties props = entry.getValue(); |
| 67 | + matches.put(entry.getKey(), new JarProperties(props.name, new ArrayList<String>(props.urls))); |
| 68 | + } |
| 69 | + } |
| 70 | + return matches; |
| 71 | + } |
| 72 | + |
| 73 | + static public HashMap<String, ArrayList<String>> findDocumentationForClass(final String s) { |
| 74 | + final HashMap<String, JarProperties> matches = findClassDocumentationURLs(s); |
| 75 | + ensureSciJavaSubURLCache(); |
| 76 | + |
| 77 | + final Pattern java8 = Pattern.compile("^(java|javax|org.omg|org.w3c|org.xml|org.ietf.jgss)\\..*$"); |
| 78 | + |
| 79 | + final HashMap<String, ArrayList<String>> class_urls = new HashMap<>(); |
| 80 | + |
| 81 | + for (final Map.Entry<String, JarProperties> entry: matches.entrySet()) { |
| 82 | + final String classname = entry.getKey(); |
| 83 | + final ArrayList<String> urls = new ArrayList<>(); |
| 84 | + class_urls.put(classname, urls); |
| 85 | + if (java8.matcher(classname).matches()) { |
| 86 | + urls.add(scijava_javadoc_URLs.get("java8") + classname.replace('.', '/') + ".html"); |
| 87 | + } else { |
| 88 | + final JarProperties props = entry.getValue(); |
| 89 | + // Find the first URL with git in it |
| 90 | + for (final String url : props.urls) { |
| 91 | + final boolean github = url.contains("/github.com"), |
| 92 | + gitlab = url.contains("/gitlab.com"); |
| 93 | + if (github || gitlab) { |
| 94 | + // Find the 5th slash, e.g. https://github.com/imglib/imglib2/ |
| 95 | + int count = 0; |
| 96 | + int last = 0; |
| 97 | + while (count < 5) { |
| 98 | + last = url.indexOf('/', last + 1); |
| 99 | + if (-1 == last) break; // less than 5 found |
| 100 | + ++count; |
| 101 | + } |
| 102 | + String urlbase = url; |
| 103 | + if (5 == count) urlbase = url.substring(0, last); // without the ending slash |
| 104 | + // Assume maven, since these URLs were found in a pom.xml: src/main/java/ |
| 105 | + urls.add(urlbase + (gitlab ? "/-" : "") + "/blob/master/src/main/java/" + classname.replace('.', '/') + ".java"); |
| 106 | + break; |
| 107 | + } |
| 108 | + } |
| 109 | + // Try to find a javadoc in the scijava website |
| 110 | + if (null != props.name) { |
| 111 | + String scijava_javadoc_url = scijava_javadoc_URLs.get(props.name.toLowerCase()); |
| 112 | + if (null == scijava_javadoc_url) { |
| 113 | + // Try cropping name at the first whitespace if any (e.g. "ImgLib2 Core Library" to "ImgLib2") |
| 114 | + final int ispace = props.name.indexOf(' '); |
| 115 | + if (-1 != ispace) { |
| 116 | + scijava_javadoc_url = scijava_javadoc_URLs.get(props.name.toLowerCase().substring(0, ispace)); |
| 117 | + } |
| 118 | + } |
| 119 | + if (null != scijava_javadoc_url) { |
| 120 | + urls.add(scijava_javadoc_url + classname.replace('.', '/') + ".html"); |
| 121 | + } else { |
| 122 | + // Try Fiji: could be a plugin |
| 123 | + Scanner scanner = null; |
| 124 | + try { |
| 125 | + final String url = scijava_javadoc_URL + "Fiji/" + classname.replace('.', '/') + ".html"; |
| 126 | + final URLConnection c = new URL(url).openConnection(); |
| 127 | + scanner = new Scanner(c.getInputStream()); |
| 128 | + while (scanner.hasNext()) { |
| 129 | + final String line = scanner.nextLine(); |
| 130 | + if (line.contains("<title>")) { |
| 131 | + if (!line.contains("<title>404")) { |
| 132 | + urls.add(url); |
| 133 | + } |
| 134 | + break; |
| 135 | + } |
| 136 | + } |
| 137 | + } catch (Exception e) { |
| 138 | + // Ignore: 404 that wasn't redirected to an error page |
| 139 | + } finally { |
| 140 | + if (null != scanner) scanner.close(); |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + return class_urls; |
| 148 | + } |
| 149 | + |
| 150 | + static public final class JarProperties { |
| 151 | + public final ArrayList<String> urls; |
| 152 | + public String name = null; |
| 153 | + public JarProperties(final String name, final ArrayList<String> urls) { |
| 154 | + this.name = name; |
| 155 | + this.urls = urls; |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + static public final HashMap<String, JarProperties> findAllClasses() { |
| 160 | + // Find all jar files |
| 161 | + final ArrayList<String> jarFilePaths = new ArrayList<String>(); |
| 162 | + final LinkedList<String> dirs = new LinkedList<>(); |
| 163 | + dirs.add(System.getProperty("java.home")); |
| 164 | + dirs.add(System.getProperty("ij.dir")); |
| 165 | + final HashSet<String> seenDirs = new HashSet<>(); |
| 166 | + while (!dirs.isEmpty()) { |
| 167 | + final String filepath = dirs.removeFirst(); |
| 168 | + final File file = new File(filepath); |
| 169 | + seenDirs.add(file.getAbsolutePath()); |
| 170 | + if (file.exists()) { |
| 171 | + if (file.isDirectory()) { |
| 172 | + for (final File child : file.listFiles()) { |
| 173 | + final String childfilepath = child.getAbsolutePath(); |
| 174 | + if (seenDirs.contains(childfilepath)) continue; |
| 175 | + if (child.isDirectory()) dirs.add(childfilepath); |
| 176 | + else if (childfilepath.endsWith(".jar")) jarFilePaths.add(childfilepath); |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + // Find all classes from all jar files |
| 182 | + final HashMap<String, JarProperties> class_urls = new HashMap<>(); |
| 183 | + final Pattern urlpattern = Pattern.compile(">(http.*?)<"); |
| 184 | + final Pattern namepattern = Pattern.compile("<name>(.*?)<"); |
| 185 | + for (final String jarpath : jarFilePaths) { |
| 186 | + JarFile jar = null; |
| 187 | + try { |
| 188 | + jar = new JarFile(jarpath); |
| 189 | + final Enumeration<JarEntry> entries = jar.entries(); |
| 190 | + final ArrayList<String> urls = new ArrayList<>(); |
| 191 | + final JarProperties props = new JarProperties(null, urls); |
| 192 | + // For every filepath in the jar zip archive |
| 193 | + while (entries.hasMoreElements()) { |
| 194 | + final JarEntry entry = entries.nextElement(); |
| 195 | + if (entry.isDirectory()) continue; |
| 196 | + if (entry.getName().endsWith(".class")) { |
| 197 | + String classname = entry.getName().replace('/', '.'); |
| 198 | + final int idollar = classname.indexOf('$'); |
| 199 | + if (-1 != idollar) { |
| 200 | + classname = classname.substring(0, idollar); // truncate at the first dollar sign |
| 201 | + } else { |
| 202 | + classname = classname.substring(0, classname.length() - 6); // without .class |
| 203 | + } |
| 204 | + class_urls.put(classname, props); |
| 205 | + } else if (entry.getName().endsWith("/pom.xml")) { |
| 206 | + final Scanner scanner = new Scanner(jar.getInputStream(entry)); |
| 207 | + while (scanner.hasNext()) { |
| 208 | + final String line = scanner.nextLine(); |
| 209 | + final Matcher matcher1 = urlpattern.matcher(line); |
| 210 | + if (matcher1.find()) { |
| 211 | + urls.add(matcher1.group(1)); |
| 212 | + } |
| 213 | + if (null == props.name) { |
| 214 | + final Matcher matcher2 = namepattern.matcher(line); |
| 215 | + if (matcher2.find()) { |
| 216 | + props.name = matcher2.group(1); |
| 217 | + } |
| 218 | + } |
| 219 | + } |
| 220 | + scanner.close(); |
| 221 | + } |
| 222 | + } |
| 223 | + } catch (IOException e) { |
| 224 | + e.printStackTrace(); |
| 225 | + } finally { |
| 226 | + if (null != jar) try { |
| 227 | + jar.close(); |
| 228 | + } catch (IOException e) { e.printStackTrace(); } |
| 229 | + } |
| 230 | + } |
| 231 | + return class_urls; |
| 232 | + } |
| 233 | +} |
0 commit comments