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.library.BuiltinSectionComparator; 35 import com.oracle.javafx.scenebuilder.kit.library.Library; 36 import com.oracle.javafx.scenebuilder.kit.library.LibraryItem; 37 import com.oracle.javafx.scenebuilder.kit.library.util.JarReport; 38 import java.io.File; 39 import java.io.FileInputStream; 40 import java.io.FileNotFoundException; 41 import java.io.IOException; 42 import java.io.InputStreamReader; 43 import java.io.LineNumberReader; 44 import java.io.PrintWriter; 45 import java.net.URLClassLoader; 46 import java.nio.file.Files; 47 import java.nio.file.Path; 48 import java.nio.file.Paths; 49 import java.nio.file.StandardCopyOption; 50 import java.util.ArrayList; 51 import java.util.Collection; 52 import java.util.Comparator; 53 import java.util.Date; 54 import java.util.List; 55 import java.util.TreeSet; 56 import javafx.application.Platform; 57 import javafx.beans.property.ReadOnlyIntegerProperty; 58 import javafx.beans.property.ReadOnlyObjectProperty; 59 import javafx.beans.property.SimpleIntegerProperty; 60 import javafx.beans.property.SimpleObjectProperty; 61 import javafx.collections.FXCollections; 62 import javafx.collections.ObservableList; 63 64 /** 65 * 66 * 67 */ 68 public class UserLibrary extends Library { 69 70 public enum State { READY, WATCHING }; 71 public static final String TAG_USER_DEFINED = "Custom"; //NOI18N 72 73 private final String path; 74 private final BuiltinSectionComparator sectionComparator 75 = new BuiltinSectionComparator(); 76 77 private final ObservableList<JarReport> jarReports = FXCollections.observableArrayList(); 78 private final ObservableList<JarReport> previousJarReports = FXCollections.observableArrayList(); 79 private final ObservableList<Path> fxmlFileReports = FXCollections.observableArrayList(); 80 private final ObservableList<Path> previousFxmlFileReports = FXCollections.observableArrayList(); 81 private final SimpleIntegerProperty explorationCountProperty = new SimpleIntegerProperty(); 82 private final SimpleObjectProperty<Date> explorationDateProperty = new SimpleObjectProperty<>(); 83 84 private State state = State.READY; 85 private Exception exception; 86 private LibraryFolderWatcher watcher; 87 private Thread watcherThread; 88 // Where we store canonical class names of items we want to exclude from 89 // the user defined one displayed in the Library panel. 90 // As a consequence an empty file means we display all items. 91 private final String filterFileName = "filter.txt"; //NOI18N 92 93 94 /* 95 * Public 96 */ 97 98 public UserLibrary(String path) { 99 this.path = path; 100 } 101 102 public String getPath() { 103 return path; 104 } 105 106 public ObservableList<JarReport> getJarReports() { 107 return jarReports; 108 } 109 110 public ObservableList<JarReport> getPreviousJarReports() { 111 return previousJarReports; 112 } 113 114 public ObservableList<Path> getFxmlFileReports() { 115 return fxmlFileReports; 116 } 117 118 public ObservableList<Path> getPreviousFxmlFileReports() { 119 return previousFxmlFileReports; 120 } 121 122 public synchronized State getState() { 123 return state; 124 } 125 126 public synchronized void startWatching() { 127 assert state == State.READY; 128 129 if (state == State.READY) { 130 assert watcher == null; 131 assert watcherThread == null; 132 133 watcher = new LibraryFolderWatcher(this); 134 watcherThread = new Thread(watcher); 135 watcherThread.setName(watcher.getClass().getSimpleName() + "(" + path + ")"); //NOI18N 136 watcherThread.setDaemon(true); 137 watcherThread.start(); 138 state = State.WATCHING; 139 } 140 } 141 142 public synchronized void stopWatching() { 143 assert state == State.WATCHING; 144 145 if (state == State.WATCHING) { 146 assert watcher != null; 147 assert watcherThread != null; 148 assert exception == null; 149 150 watcherThread.interrupt(); 151 152 try { 153 watcherThread.join(); 154 } catch(InterruptedException x) { 155 x.printStackTrace(); 156 } finally { 157 watcher = null; 158 watcherThread = null; 159 state = State.READY; 160 161 // In READY state, we release the class loader. 162 // This enables library import to manipulate jar files. 163 changeClassLoader(null); 164 previousJarReports.clear(); 165 } 166 } 167 } 168 169 public int getExplorationCount() { 170 return explorationCountProperty.get(); 171 } 172 173 public ReadOnlyIntegerProperty explorationCountProperty() { 174 return explorationCountProperty; 175 } 176 177 public Object getExplorationDate() { 178 return explorationDateProperty.get(); 179 } 180 181 public ReadOnlyObjectProperty<Date> explorationDateProperty() { 182 return explorationDateProperty; 183 } 184 185 public void setFilter(List<String> classnames) throws FileNotFoundException, IOException { 186 if (classnames != null && classnames.size() > 0) { 187 File filterFile = new File(getFilterFileName()); 188 // TreeSet to get natural order sorting and no duplicates 189 TreeSet<String> allClassnames = new TreeSet<>(); 190 191 for (String classname : classnames) { 192 allClassnames.add(classname); 193 } 194 195 Path filterFilePath = Paths.get(getPath(), filterFileName); 196 Path formerFilterFilePath = Paths.get(getPath(), filterFileName + ".tmp"); //NOI18N 197 Files.deleteIfExists(formerFilterFilePath); 198 199 try { 200 // Rename already existing filter file so that we can rollback 201 if (Files.exists(filterFilePath)) { 202 Files.move(filterFilePath, formerFilterFilePath, StandardCopyOption.ATOMIC_MOVE); 203 } 204 205 // Create the new filter file 206 Files.createFile(filterFilePath); 207 208 // Write content of the new filter file 209 try (PrintWriter writer = new PrintWriter(filterFile, "UTF-8")) { //NOI18N 210 for (String classname : allClassnames) { 211 writer.write(classname + "\n"); //NOI18N 212 } 213 } 214 215 // Delete the former filter file 216 if (Files.exists(formerFilterFilePath)) { 217 Files.delete(formerFilterFilePath); 218 } 219 } catch (IOException ioe) { 220 // Rollback 221 if (Files.exists(formerFilterFilePath)) { 222 Files.move(formerFilterFilePath, filterFilePath, StandardCopyOption.ATOMIC_MOVE); 223 } 224 throw (ioe); 225 } 226 } 227 } 228 229 public List<String> getFilter() throws FileNotFoundException, IOException { 230 List<String> res = new ArrayList<>(); 231 File filterFile = new File(getFilterFileName()); 232 233 if (filterFile.exists()) { 234 try (LineNumberReader reader = new LineNumberReader(new InputStreamReader(new FileInputStream(filterFile), "UTF-8"))) { //NOI18N 235 String line; 236 while ((line = reader.readLine()) != null) { 237 res.add(line); 238 } 239 } 240 } 241 242 return res; 243 } 244 245 /* 246 * Package 247 */ 248 249 String getFilterFileName() { 250 return getPath() + File.separator + filterFileName; 251 } 252 253 void updateJarReports(Collection<JarReport> newJarReports) { 254 if (Platform.isFxApplicationThread()) { 255 previousJarReports.setAll(jarReports); 256 jarReports.setAll(newJarReports); 257 } else { 258 Platform.runLater(() -> { 259 previousJarReports.setAll(jarReports); 260 jarReports.setAll(newJarReports); 261 }); 262 } 263 } 264 265 void updateFxmlFileReports(Collection<Path> newFxmlFileReports) { 266 if (Platform.isFxApplicationThread()) { 267 previousFxmlFileReports.setAll(fxmlFileReports); 268 fxmlFileReports.setAll(newFxmlFileReports); 269 } else { 270 Platform.runLater(() -> { 271 previousFxmlFileReports.setAll(fxmlFileReports); 272 fxmlFileReports.setAll(newFxmlFileReports); 273 }); 274 } 275 } 276 277 void setItems(Collection<LibraryItem> items) { 278 if (Platform.isFxApplicationThread()) { 279 itemsProperty.setAll(items); 280 } else { 281 Platform.runLater(() -> itemsProperty.setAll(items)); 282 } 283 } 284 285 void addItems(Collection<LibraryItem> items) { 286 if (Platform.isFxApplicationThread()) { 287 itemsProperty.addAll(items); 288 } else { 289 Platform.runLater(() -> itemsProperty.addAll(items)); 290 } 291 } 292 293 void updateClassLoader(ClassLoader newClassLoader) { 294 if (Platform.isFxApplicationThread()) { 295 changeClassLoader(newClassLoader); 296 } else { 297 Platform.runLater(() -> changeClassLoader(newClassLoader)); 298 } 299 } 300 301 void updateExplorationCount(int count) { 302 if (Platform.isFxApplicationThread()) { 303 explorationCountProperty.set(count); 304 } else { 305 Platform.runLater(() -> explorationCountProperty.set(count)); 306 } 307 } 308 309 void updateExplorationDate(Date date) { 310 if (Platform.isFxApplicationThread()) { 311 explorationDateProperty.set(date); 312 } else { 313 Platform.runLater(() -> explorationDateProperty.set(date)); 314 } 315 } 316 317 /* 318 * Library 319 */ 320 @Override 321 public Comparator<String> getSectionComparator() { 322 return sectionComparator; 323 } 324 325 /* 326 * Private 327 */ 328 329 private void changeClassLoader(ClassLoader newClassLoader) { 330 assert Platform.isFxApplicationThread(); 331 332 /* 333 * Before changing to the new class loader, 334 * we invoke URLClassLoader.close() on the existing one 335 * so that it releases its associated jar files. 336 */ 337 final ClassLoader classLoader = classLoaderProperty.get(); 338 if (classLoader instanceof URLClassLoader) { 339 final URLClassLoader urlClassLoader = (URLClassLoader) classLoader; 340 try { 341 urlClassLoader.close(); 342 } catch(IOException x) { 343 x.printStackTrace(); 344 } 345 } 346 347 // Now moves to the new class loader 348 classLoaderProperty.set(newClassLoader); 349 } 350 351 /* 352 * Debug 353 */ 354 355 public static void main(String[] args) throws Exception { 356 final String path = "/Users/elp/Desktop/MyLib"; //NOI18N 357 final UserLibrary lib = new UserLibrary(path); 358 lib.startWatching(); 359 System.out.println("Starting to watch for 20 s"); //NOI18N 360 Thread.sleep(20 * 1000); 361 System.out.println("Stopping to watch for 20 s"); //NOI18N 362 lib.stopWatching(); 363 Thread.sleep(20 * 1000); 364 System.out.println("Exiting"); //NOI18N 365 } 366 }