1 /*
   2  * Copyright (c) 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 
  33 package com.oracle.javafx.scenebuilder.app;
  34 
  35 import com.oracle.javafx.scenebuilder.app.i18n.I18N;
  36 import com.oracle.javafx.scenebuilder.kit.editor.EditorController;
  37 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument;
  38 import com.oracle.javafx.scenebuilder.kit.util.FileWatcher;
  39 
  40 import java.io.File;
  41 import java.io.IOException;
  42 import java.net.URISyntaxException;
  43 import java.nio.file.Path;
  44 import java.util.ArrayList;
  45 import java.util.List;
  46 
  47 import javafx.beans.value.ChangeListener;
  48 import javafx.collections.ObservableList;
  49 
  50 /**
  51  *
  52  */
  53 public class DocumentWatchingController implements FileWatcher.Delegate {
  54 
  55     private final DocumentWindowController documentWindowController;
  56     private final EditorController editorController;
  57     private final ResourceController resourceController;
  58     private final SceneStyleSheetMenuController sceneStyleSheetMenuController;
  59     private final FileWatcher fileWatcher
  60             = new FileWatcher(2000 /* ms */, this, DocumentWindowController.class.getSimpleName());
  61 
  62 
  63     public DocumentWatchingController(DocumentWindowController documentWindowController) {
  64         this.documentWindowController = documentWindowController;
  65         this.editorController = documentWindowController.getEditorController();
  66         this.resourceController = documentWindowController.getResourceController();
  67         this.sceneStyleSheetMenuController = documentWindowController.getSceneStyleSheetMenuController();
  68 
  69         this.editorController.sceneStyleSheetProperty().addListener(
  70                 (ChangeListener<ObservableList<File>>) (ov, t,
  71                         t1) -> update());
  72     }
  73 
  74     public void start() {
  75         fileWatcher.start();
  76     }
  77 
  78 
  79     public void stop() {
  80         fileWatcher.stop();
  81     }
  82 
  83 
  84     public void update() {
  85         /*
  86          * The file watcher associated to this document window controller watches:
  87          *  1) the file holding the FXML document (if any)
  88          *  2) the resource file set in the Preview menu (if any)
  89          *  3) the style sheets files set in the Preview menu (if any)
  90          */
  91 
  92         final List<Path> targets = new ArrayList<>();
  93 
  94         // 1)
  95         final FXOMDocument fxomDocument = editorController.getFxomDocument();
  96         if ((fxomDocument != null) && (fxomDocument.getLocation() != null)) {
  97             try {
  98                 final File fxmlFile = new File(fxomDocument.getLocation().toURI());
  99                 targets.add(fxmlFile.toPath());
 100             } catch(URISyntaxException x) {
 101                 throw new IllegalStateException("Bug", x); //NOI18N
 102             }
 103         }
 104 
 105         // 2)
 106         if (resourceController.getResourceFile() != null) {
 107             targets.add(resourceController.getResourceFile().toPath());
 108         }
 109 
 110         // 3)
 111         if (editorController.getSceneStyleSheets() != null) {
 112             for (File sceneStyleSheet : editorController.getSceneStyleSheets()) {
 113                 targets.add(sceneStyleSheet.toPath());
 114             }
 115         }
 116 
 117         fileWatcher.setTargets(targets);
 118     }
 119 
 120     public void removeDocumentTarget() {
 121         final FXOMDocument fxomDocument = editorController.getFxomDocument();
 122         assert fxomDocument != null;
 123         assert fxomDocument.getLocation() != null;
 124 
 125         try {
 126             final File fxmlFile = new File(fxomDocument.getLocation().toURI());
 127             assert fileWatcher.getTargets().contains(fxmlFile.toPath());
 128             fileWatcher.removeTarget(fxmlFile.toPath());
 129         } catch(URISyntaxException x) {
 130             throw new IllegalStateException("Bug", x); //NOI18N
 131         }
 132     }
 133 
 134     /*
 135      * FileWatcher.Delegate
 136      */
 137 
 138     @Override
 139     public void fileWatcherDidWatchTargetCreation(Path target) {
 140         // Ignored
 141     }
 142 
 143     @Override
 144     public void fileWatcherDidWatchTargetDeletion(Path target) {
 145         if (isPathMatchingResourceLocation(target)) {
 146             // Resource file has disappeared
 147             resourceController.performRemoveResource();
 148             // Call above has invoked
 149             //      - FXOMDocument.refreshSceneGraph()
 150             //      - DocumentWatchingController.update()
 151             editorController.getMessageLog().logInfoMessage("log.info.file.deleted",
 152                     I18N.getBundle(), target.getFileName());
 153         } else if (isPathMatchingSceneStyleSheet(target)) {
 154             sceneStyleSheetMenuController.performRemoveSceneStyleSheet(target.toFile());
 155             // Call above has invoked
 156             //      - FXOMDocument.reapplyCSS()
 157             //      - DocumentWatchingController.update()
 158             editorController.getMessageLog().logInfoMessage("log.info.file.deleted",
 159                     I18N.getBundle(), target.getFileName());
 160         }
 161         /*
 162          * Else it's the document file which has disappeared :
 163          * We ignore this event : file will be recreated when user runs
 164          * the save command.
 165          */
 166     }
 167 
 168     @Override
 169     public void fileWatcherDidWatchTargetModification(Path target) {
 170         if (isPathMatchingResourceLocation(target)) {
 171             // Resource file has been modified -> refresh the scene graph
 172             resourceController.performReloadResource();
 173             // Call above has invoked FXOMDocument.refreshSceneGraph()
 174             editorController.getMessageLog().logInfoMessage("log.info.reload",
 175                     I18N.getBundle(), target.getFileName());
 176 
 177         } else if (isPathMatchingDocumentLocation(target)) {
 178             if (documentWindowController.isDocumentDirty() == false) {
 179                 // Try to reload the fxml text on disk
 180                 try {
 181                     documentWindowController.reload();
 182                     editorController.getMessageLog().logInfoMessage("log.info.reload",
 183                             I18N.getBundle(), target.getFileName());
 184                 } catch(IOException x) {
 185                     // Here we silently ignore the failure :
 186                     // loadFromFile() has failed but left the document unchanged.
 187                 }
 188             }
 189         } else if (isPathMatchingSceneStyleSheet(target)) {
 190             final FXOMDocument fxomDocument = editorController.getFxomDocument();
 191             if (fxomDocument != null) {
 192                 fxomDocument.reapplyCSS(target);
 193                 editorController.getMessageLog().logInfoMessage("log.info.reload",
 194                         I18N.getBundle(), target.getFileName());
 195             }
 196         }
 197     }
 198 
 199 
 200     /*
 201      * Private
 202      */
 203 
 204     private boolean isPathMatchingDocumentLocation(Path p) {
 205         final boolean result;
 206 
 207         final FXOMDocument fxomDocument = editorController.getFxomDocument();
 208         if ((fxomDocument != null) && (fxomDocument.getLocation() != null)) {
 209             try {
 210                 final File fxmlFile = new File(fxomDocument.getLocation().toURI());
 211                 result = p.equals(fxmlFile.toPath());
 212             } catch(URISyntaxException x) {
 213                 throw new IllegalStateException("Bug", x); //NOI18N
 214             }
 215         } else {
 216             result = false;
 217         }
 218 
 219         return result;
 220     }
 221 
 222     private boolean isPathMatchingResourceLocation(Path p) {
 223         final boolean result;
 224 
 225         if (resourceController.getResourceFile() != null) {
 226             result = p.equals(resourceController.getResourceFile().toPath());
 227         } else {
 228             result = false;
 229         }
 230 
 231         return result;
 232     }
 233 
 234 
 235     private boolean isPathMatchingSceneStyleSheet(Path p) {
 236         final boolean result;
 237 
 238         if (editorController.getSceneStyleSheets() != null) {
 239             result = editorController.getSceneStyleSheets().contains(p.toFile());
 240         } else {
 241             result = false;
 242         }
 243 
 244         return result;
 245     }
 246 
 247 }