1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.kit.library.user; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N; 35 import com.oracle.javafx.scenebuilder.kit.editor.images.ImageUtils; 36 import com.oracle.javafx.scenebuilder.kit.library.BuiltinLibrary; 37 import com.oracle.javafx.scenebuilder.kit.library.LibraryItem; 38 import com.oracle.javafx.scenebuilder.kit.library.util.JarExplorer; 39 import com.oracle.javafx.scenebuilder.kit.library.util.JarReport; 40 import com.oracle.javafx.scenebuilder.kit.library.util.JarReportEntry; 41 import java.io.FileInputStream; 42 import java.io.IOException; 43 import java.io.InputStreamReader; 44 import java.io.LineNumberReader; 45 import java.net.MalformedURLException; 46 import java.net.URL; 47 import java.net.URLClassLoader; 48 import java.nio.file.DirectoryStream; 49 import java.nio.file.Files; 50 import java.nio.file.Path; 51 import java.nio.file.Paths; 52 import java.nio.file.StandardWatchEventKinds; 53 import java.nio.file.WatchEvent; 54 import java.nio.file.WatchKey; 55 import java.nio.file.WatchService; 56 import java.util.ArrayList; 57 import java.util.Collection; 58 import java.util.Date; 59 import java.util.HashSet; 60 import java.util.List; 61 import java.util.Locale; 62 import java.util.Set; 63 import java.util.logging.Logger; 64 65 /** 66 * 67 * 68 */ 69 class LibraryFolderWatcher implements Runnable { 70 71 private final UserLibrary library; 72 private enum FILE_TYPE {FXML, JAR}; 73 74 public LibraryFolderWatcher(UserLibrary library) { 75 this.library = library; 76 } 77 78 /* 79 * Runnable 80 */ 81 82 @Override 83 public void run() { 84 85 try { 86 library.updateExplorationCount(0); 87 library.updateExplorationDate(new Date()); 88 runDiscovery(); 89 runWatching(); 90 } catch(InterruptedException x) { 91 // Let's stop 92 } 93 } 94 95 96 /* 97 * Private 98 */ 99 100 101 private void runDiscovery() throws InterruptedException { 102 final Path folder = Paths.get(library.getPath()); 103 104 // First put the builtin items in the library 105 library.setItems(BuiltinLibrary.getLibrary().getItems()); 106 107 // Now attempts to discover the user library folder 108 boolean retry; 109 do { 110 try (DirectoryStream<Path> stream = Files.newDirectoryStream(folder)) { 111 final Set<Path> currentJars = new HashSet<>(); 112 final Set<Path> currentFxmls = new HashSet<>(); 113 for (Path entry: stream) { 114 if (isJarPath(entry)) { 115 currentJars.add(entry); 116 } else if (isFxmlPath(entry)) { 117 currentFxmls.add(entry); 118 } 119 } 120 121 updateLibrary(currentFxmls); 122 exploreAndUpdateLibrary(currentJars); 123 retry = false; 124 } catch(IOException x) { 125 Thread.sleep(2000 /* ms */); 126 retry = true; 127 } finally { 128 library.updateExplorationCount(library.getExplorationCount()+1); 129 } 130 } 131 while (retry); 132 133 } 134 135 private void runWatching() throws InterruptedException { 136 while (true) { 137 final Path folder = Paths.get(library.getPath()); 138 139 WatchService watchService = null; 140 while (watchService == null) { 141 try { 142 watchService = folder.getFileSystem().newWatchService(); 143 } catch(IOException x) { 144 System.out.println("FileSystem.newWatchService() failed"); //NOI18N 145 System.out.println("Sleeping..."); //NOI18N 146 Thread.sleep(1000 /* ms */); 147 } 148 } 149 150 WatchKey watchKey = null; 151 while ((watchKey == null) || (watchKey.isValid() == false)) { 152 try { 153 watchKey = folder.register(watchService, 154 StandardWatchEventKinds.ENTRY_CREATE, 155 StandardWatchEventKinds.ENTRY_DELETE, 156 StandardWatchEventKinds.ENTRY_MODIFY); 157 158 WatchKey wk; 159 do { 160 wk = watchService.take(); 161 assert wk == watchKey; 162 163 boolean isDirty = false; 164 for (WatchEvent<?> e: wk.pollEvents()) { 165 final WatchEvent.Kind<?> kind = e.kind(); 166 final Object context = e.context(); 167 168 if (kind == StandardWatchEventKinds.ENTRY_CREATE 169 || kind == StandardWatchEventKinds.ENTRY_DELETE 170 || kind == StandardWatchEventKinds.ENTRY_MODIFY) { 171 assert context instanceof Path; 172 if (isJarPath((Path)context) || isFxmlPath((Path)context)) { 173 isDirty = true; 174 } 175 } else { 176 assert kind == StandardWatchEventKinds.OVERFLOW; 177 } 178 } 179 180 // We reconstruct a full set from scratch as soon as the 181 // dirty flag is set. 182 if (isDirty) { 183 // First put the builtin items in the library 184 library.setItems(BuiltinLibrary.getLibrary().getItems()); 185 186 final Set<Path> fxmls = new HashSet<>(); 187 fxmls.addAll(getAllFiles(FILE_TYPE.FXML)); 188 updateLibrary(fxmls); 189 190 final Set<Path> jars = new HashSet<>(); 191 jars.addAll(getAllFiles(FILE_TYPE.JAR)); 192 exploreAndUpdateLibrary(jars); 193 194 library.updateExplorationCount(library.getExplorationCount()+1); 195 } 196 } while (wk.reset()); 197 } catch(IOException x) { 198 Thread.sleep(1000 /* ms */); 199 } 200 } 201 202 } 203 } 204 205 206 private Set<Path> getAllFiles(FILE_TYPE fileType) throws IOException { 207 Set<Path> res = new HashSet<>(); 208 final Path folder = Paths.get(library.getPath()); 209 210 try (DirectoryStream<Path> ds = Files.newDirectoryStream(folder)) { 211 for (Path p : ds) { 212 switch (fileType) { 213 case FXML: 214 if (isFxmlPath(p)) { 215 res.add(p); 216 } 217 break; 218 case JAR: 219 if (isJarPath(p)) { 220 res.add(p); 221 } 222 break; 223 default: 224 break; 225 } 226 } 227 } 228 229 return res; 230 } 231 232 233 private static boolean isJarPath(Path path) { 234 final String pathString = path.toString().toLowerCase(Locale.ROOT); 235 return pathString.endsWith(".jar"); //NOI18N 236 } 237 238 239 private static boolean isFxmlPath(Path path) { 240 final String pathString = path.toString().toLowerCase(Locale.ROOT); 241 return pathString.endsWith(".fxml"); //NOI18N 242 } 243 244 245 private void updateLibrary(Collection<Path> paths) throws IOException { 246 final List<LibraryItem> newItems = new ArrayList<>(); 247 248 for (Path path : paths) { 249 newItems.add(makeLibraryItem(path)); 250 } 251 252 library.addItems(newItems); 253 library.updateFxmlFileReports(paths); 254 library.updateExplorationDate(new Date()); 255 } 256 257 258 private LibraryItem makeLibraryItem(Path path) throws IOException { 259 final URL iconURL = ImageUtils.getNodeIconURL(null); 260 String fileName = path.getFileName().toString(); 261 String itemName = fileName.substring(0, fileName.indexOf(".fxml")); //NOI18N 262 String fxmlText = ""; //NOI18N 263 StringBuilder buf = new StringBuilder(); 264 265 try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(new FileInputStream(path.toFile()), "UTF-8"))) { //NOI18N 266 String line; 267 while ((line = reader.readLine()) != null) { 268 buf.append(line).append("\n"); //NOI18N 269 } 270 271 fxmlText = buf.toString(); 272 } 273 274 final LibraryItem res = new LibraryItem(itemName, UserLibrary.TAG_USER_DEFINED, fxmlText, iconURL, library); 275 return res; 276 } 277 278 279 private void exploreAndUpdateLibrary(Collection<Path> jars) throws IOException { 280 281 // 1) we create a classloader 282 // 2) we explore all the jars 283 // 3) we construct a list of library items 284 // 4) we update the user library with the class loader and items 285 286 // 1) 287 final ClassLoader classLoader; 288 if (jars.isEmpty()) { 289 classLoader = null; 290 } else { 291 classLoader = new URLClassLoader(makeURLArrayFromPaths(jars)); 292 } 293 294 // 2) 295 final List<JarReport> jarReports = new ArrayList<>(); 296 for (Path currentJar : jars) { 297 Logger.getLogger(this.getClass().getSimpleName()).info(I18N.getString("log.info.explore.jar", currentJar)); 298 final JarExplorer explorer = new JarExplorer(currentJar); 299 jarReports.add(explorer.explore(classLoader)); 300 } 301 302 // 3) 303 final List<LibraryItem> newItems = new ArrayList<>(); 304 for (JarReport jarReport : jarReports) { 305 newItems.addAll(makeLibraryItems(jarReport)); 306 } 307 308 // 4) 309 library.updateClassLoader(classLoader); 310 library.addItems(newItems); 311 library.updateJarReports(new ArrayList<>(jarReports)); 312 library.updateExplorationDate(new Date()); 313 } 314 315 316 private Collection<LibraryItem> makeLibraryItems(JarReport jarReport) throws IOException { 317 final List<LibraryItem> result = new ArrayList<>(); 318 final URL iconURL = ImageUtils.getNodeIconURL(null); 319 final List<String> excludedItems = library.getFilter(); 320 321 for (JarReportEntry e : jarReport.getEntries()) { 322 if ((e.getStatus() == JarReportEntry.Status.OK) && e.isNode()) { 323 // We filter out items listed in the excluded list, based on canonical name of the class. 324 final String canonicalName = e.getKlass().getCanonicalName(); 325 if (! excludedItems.contains(canonicalName)) { 326 final String name = e.getKlass().getSimpleName(); 327 final String fxmlText = JarExplorer.makeFxmlText(e.getKlass()); 328 result.add(new LibraryItem(name, UserLibrary.TAG_USER_DEFINED, fxmlText, iconURL, library)); 329 } 330 } 331 } 332 333 return result; 334 } 335 336 337 private URL[] makeURLArrayFromPaths(Collection<Path> paths) { 338 final URL[] result = new URL[paths.size()]; 339 int i = 0; 340 for (Path p : paths) { 341 try { 342 result[i++] = p.toUri().toURL(); 343 } catch(MalformedURLException x) { 344 throw new RuntimeException("Bug in " + getClass().getSimpleName(), x); //NOI18N 345 } 346 } 347 348 return result; 349 } 350 351 }