You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2013/07/20 11:49:41 UTC

svn commit: r1505114 - in /cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler: ProjectController.java ProjectFileChangeTracker.java ProjectWatchdog.java util/FileWatchdog.java

Author: aadamchik
Date: Sat Jul 20 09:49:41 2013
New Revision: 1505114

URL: http://svn.apache.org/r1505114
Log:
CAY-1852  Straighten thread model and synchronization in the Modeler

refactoring 'ProjectWatchdog'

Added:
    cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectFileChangeTracker.java
Removed:
    cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectWatchdog.java
    cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/FileWatchdog.java
Modified:
    cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java

Modified: cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java?rev=1505114&r1=1505113&r2=1505114&view=diff
==============================================================================
--- cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java (original)
+++ cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java Sat Jul 20 09:49:41 2013
@@ -238,7 +238,7 @@ public class ProjectController extends C
      * Project files watcher. When project file is changed, user will be asked
      * to confirm loading the changes
      */
-    ProjectWatchdog watchdog;
+    private ProjectFileChangeTracker watchdog;
 
     public ProjectController(CayenneModelerController parent) {
         super(parent);
@@ -275,7 +275,8 @@ public class ProjectController extends C
                 }
             } else {
                 if (watchdog == null) {
-                    watchdog = new ProjectWatchdog(this);
+                    watchdog = new ProjectFileChangeTracker(this);
+                    watchdog.setDaemon(true);
                     watchdog.start();
                 }
 
@@ -1617,7 +1618,7 @@ public class ProjectController extends C
     /**
      * @return the project files' watcher
      */
-    public ProjectWatchdog getProjectWatcher() {
+    public ProjectFileChangeTracker getProjectWatcher() {
         return watchdog;
     }
 

Added: cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectFileChangeTracker.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectFileChangeTracker.java?rev=1505114&view=auto
==============================================================================
--- cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectFileChangeTracker.java (added)
+++ cayenne/main/trunk/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectFileChangeTracker.java Sat Jul 20 09:49:41 2013
@@ -0,0 +1,292 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.modeler;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.modeler.action.OpenProjectAction;
+import org.apache.cayenne.modeler.action.SaveAction;
+import org.apache.cayenne.modeler.dialog.FileDeletedDialog;
+import org.apache.cayenne.project.Project;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * ProjectWatchdog class is responsible for tracking changes in cayenne.xml and
+ * other Cayenne project files
+ * 
+ */
+public class ProjectFileChangeTracker extends Thread {
+
+    private static final Log log = LogFactory.getLog(ProjectFileChangeTracker.class);
+
+    /**
+     * The default delay between every file modification check
+     */
+    private static final long DEFAULT_DELAY = 4000;
+
+    /**
+     * The names of the files to observe for changes.
+     */
+    protected Map<String, FileInfo> files;
+    protected boolean paused;
+    protected ProjectController mediator;
+
+    /**
+     * Creates new watchdog for a specified project
+     */
+    public ProjectFileChangeTracker(ProjectController mediator) {
+
+        this.files = new ConcurrentHashMap<String, FileInfo>();
+        this.mediator = mediator;
+
+        setName("cayenne-modeler-file-change-tracker");
+    }
+
+    /**
+     * Reloads files to watch from the project. Useful when project's structure
+     * has changed
+     */
+    public void reconfigure() {
+        pauseWatching();
+
+        removeAllFiles();
+
+        Project project = mediator.getProject();
+
+        // check if project exists and has been saved at least once.
+        if (project != null && project.getConfigurationResource() != null) {
+            String projectPath = project.getConfigurationResource().getURL().getPath() + File.separator;
+            addFile(projectPath);
+
+            Iterator<DataMap> it = ((DataChannelDescriptor) project.getRootNode()).getDataMaps().iterator();
+            while (it.hasNext()) {
+                DataMap dm = it.next();
+                addFile(dm.getConfigurationSource().getURL().getPath());
+            }
+
+        }
+
+        resumeWatching();
+    }
+
+    protected void doOnChange() {
+
+        SwingUtilities.invokeLater(new Runnable() {
+
+            public void run() {
+                if (showConfirmation("One or more project files were changed by external program. "
+                        + "Do you want to load the changes?")) {
+
+                    // Currently we are reloading all project
+                    if (mediator.getProject() != null) {
+
+                        File fileDirectory = new File(mediator.getProject().getConfigurationResource().getURL()
+                                .getPath());
+                        Application.getInstance().getActionManager().getAction(OpenProjectAction.class)
+                                .openProject(fileDirectory);
+                    }
+
+                } else {
+                    mediator.setDirty(true);
+                }
+            }
+        });
+    }
+
+    protected void doOnRemove() {
+        if (mediator.getProject() != null) {
+
+            SwingUtilities.invokeLater(new Runnable() {
+
+                public void run() {
+                    FileDeletedDialog dialog = new FileDeletedDialog(Application.getFrame());
+                    dialog.show();
+
+                    if (dialog.shouldSave()) {
+                        Application.getInstance().getActionManager().getAction(SaveAction.class).performAction(null);
+                    } else if (dialog.shouldClose()) {
+                        Application.getInstance().getFrameController().projectClosedAction();
+                    } else {
+                        mediator.setDirty(true);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Shows confirmation dialog
+     */
+    private boolean showConfirmation(String message) {
+        return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Application.getFrame(), message, "File changed",
+                JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+    }
+
+    /**
+     * Adds a new file to watch
+     * 
+     * @param location
+     *            path of file
+     */
+    public void addFile(String location) {
+        try {
+            files.put(location, new FileInfo(location));
+        } catch (SecurityException e) {
+            log.error("SecurityException adding file " + location, e);
+        }
+    }
+
+    /**
+     * Turns off watching for a specified file
+     * 
+     * @param location
+     *            path of file
+     */
+    public void removeFile(String location) {
+        files.remove(location);
+    }
+
+    /**
+     * Turns off watching for all files
+     */
+    public void removeAllFiles() {
+        files.clear();
+    }
+
+    protected void check() {
+        if (paused) {
+            return;
+        }
+
+        boolean hasChanges = false;
+        boolean hasDeletions = false;
+
+        for (Iterator<FileInfo> it = files.values().iterator(); it.hasNext();) {
+            FileInfo fi = it.next();
+
+            boolean fileExists;
+            try {
+                fileExists = fi.getFile().exists();
+            } catch (SecurityException e) {
+                log.error("SecurityException checking file " + fi.getFile().getPath(), e);
+
+                // we still process with other files
+                continue;
+            }
+
+            if (fileExists) {
+                // this can also throw a SecurityException
+                long l = fi.getFile().lastModified();
+                if (l > fi.getLastModified()) {
+                    // however, if we reached this point this is very unlikely.
+                    fi.setLastModified(l);
+                    hasChanges = true;
+                }
+            }
+            // the file has been removed
+            else if (fi.getLastModified() != -1) {
+                hasDeletions = true;
+                it.remove(); // no point to watch the file now
+            }
+        }
+
+        if (hasDeletions) {
+            doOnRemove();
+        } else if (hasChanges) {
+            doOnChange();
+        }
+    }
+
+    public void run() {
+        while (true) {
+            try {
+                Thread.sleep(DEFAULT_DELAY);
+                check();
+            } catch (InterruptedException e) {
+                // someone asked to stop
+                return;
+            }
+        }
+    }
+
+    /**
+     * Tells watcher to pause watching for some time. Useful before changing
+     * files
+     */
+    public void pauseWatching() {
+        paused = true;
+    }
+
+    /**
+     * Resumes watching for files
+     */
+    public void resumeWatching() {
+        paused = false;
+    }
+
+    /**
+     * Class to store information about files (last modification time & File
+     * pointer)
+     */
+    protected class FileInfo {
+
+        /**
+         * Exact java.io.File object, may not be null
+         */
+        File file;
+
+        /**
+         * Time the file was modified
+         */
+        long lastModified;
+
+        /**
+         * Creates new object
+         * 
+         * @param location
+         *            the file path
+         */
+        public FileInfo(String location) {
+            file = new File(location);
+            lastModified = file.exists() ? file.lastModified() : -1;
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        public long getLastModified() {
+            return lastModified;
+        }
+
+        public void setLastModified(long l) {
+            lastModified = l;
+        }
+    }
+
+}