cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From aadamc...@apache.org
Subject svn commit: r656473 - in /cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler: ProjectController.java ProjectWatchdog.java action/SaveAsAction.java dialog/FileDeletedDialog.java util/FileWatchdog.java
Date Thu, 15 May 2008 00:33:42 GMT
Author: aadamchik
Date: Wed May 14 17:33:42 2008
New Revision: 656473

URL: http://svn.apache.org/viewvc?rev=656473&view=rev
Log:
CAY-1056 Detection of the project file changes on disk.

Added:
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectWatchdog.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/FileDeletedDialog.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/FileWatchdog.java
Modified:
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java
    cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SaveAsAction.java

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java?rev=656473&r1=656472&r2=656473&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java
(original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectController.java
Wed May 14 17:33:42 2008
@@ -220,6 +220,12 @@
     protected CircularArray controllerStateHistory;
     protected int maxHistorySize = 20;
 
+    /**
+     * Project files watcher. When project file is changed, user will be asked to confirm
+     * loading the changes
+     */
+    protected ProjectWatchdog watchdog;
+
     public ProjectController(CayenneModelerController parent) {
         super(parent);
         this.listenerList = new EventListenerList();
@@ -236,8 +242,29 @@
     }
 
     public void setProject(Project currentProject) {
-        this.project = currentProject;
-        this.projectPreferences = null;
+        if (this.project != currentProject) // strange enough, this method is called twice
+        // during project opening. Not to disturb
+        // watchdog extra time, adding this check
+        {
+            this.project = currentProject;
+            this.projectPreferences = null;
+
+            if (project == null) // null project -> no files to watch
+            {
+                if (watchdog != null) {
+                    watchdog.interrupt();
+                    watchdog = null;
+                }
+            }
+            else {
+                if (watchdog == null) {
+                    watchdog = new ProjectWatchdog(this);
+                    watchdog.start();
+                }
+
+                watchdog.reconfigure();
+            }
+        }
     }
 
     /**
@@ -1293,8 +1320,8 @@
     /** Notifies all listeners of the change(add, remove) and does the change. */
     public void fireDbRelationshipEvent(RelationshipEvent e) {
         setDirty(true);
-        
-        if(e.getId() == MapEvent.CHANGE && e.getEntity() instanceof DbEntity) {
+
+        if (e.getId() == MapEvent.CHANGE && e.getEntity() instanceof DbEntity) {
             ((DbEntity) e.getEntity()).dbRelationshipChanged(e);
         }
 
@@ -1449,7 +1476,6 @@
         return currentState.callbackMethod;
     }
 
-
     /**
      * @return currently selecte entity listener class
      */
@@ -1473,14 +1499,16 @@
 
     /**
      * adds callback method manipulation listener
+     * 
      * @param listener listener
      */
     public void addCallbackMethodListener(CallbackMethodListener listener) {
         listenerList.add(CallbackMethodListener.class, listener);
     }
-    
+
     /**
      * fires callback method manipulation event
+     * 
      * @param e event
      */
     public void fireCallbackMethodEvent(CallbackMethodEvent e) {
@@ -1508,6 +1536,7 @@
 
     /**
      * adds listener class manipulation listener
+     * 
      * @param listener listener
      */
     public void addEntityListenerListener(EntityListenerListener listener) {
@@ -1516,6 +1545,7 @@
 
     /**
      * fires entity listener manipulation event
+     * 
      * @param e event
      */
     public void fireEntityListenerEvent(EntityListenerEvent e) {
@@ -1540,4 +1570,11 @@
             }
         }
     }
+
+    /**
+     * @return the project files' watcher
+     */
+    public ProjectWatchdog getProjectWatcher() {
+        return watchdog;
+    }
 }

Added: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectWatchdog.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectWatchdog.java?rev=656473&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectWatchdog.java
(added)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ProjectWatchdog.java
Wed May 14 17:33:42 2008
@@ -0,0 +1,131 @@
+/*****************************************************************
+ *   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.List;
+
+import javax.swing.JOptionPane;
+
+import org.apache.cayenne.modeler.action.OpenProjectAction;
+import org.apache.cayenne.modeler.action.SaveAsAction;
+import org.apache.cayenne.modeler.dialog.FileDeletedDialog;
+import org.apache.cayenne.modeler.util.FileWatchdog;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.ProjectFile;
+
+/**
+ * ProjectWatchdog class is responsible for tracking changes in cayenne.xml and other
+ * Cayenne project files
+ * 
+ * @author Andrey Razumovsky
+ */
+public class ProjectWatchdog extends FileWatchdog {
+
+    /**
+     * A project to watch
+     */
+    protected ProjectController mediator;
+
+    /**
+     * Creates new watchdog for a specified project
+     */
+    public ProjectWatchdog(ProjectController mediator) {
+        setName("cayenne-project-watchdog");
+        this.mediator = mediator;
+        setSingleNotification(true); // one message is more than enough
+    }
+
+    /**
+     * Reloads files to watch from the project. Useful when project's structure has
+     * changed
+     */
+    public void reconfigure() {
+        pauseWatching();
+
+        removeAllFiles();
+
+        Project project = mediator.getProject();
+        if (project != null // project opened
+                && project.getProjectDirectory() != null) // not new project
+        {
+            String projectPath = project.getProjectDirectory().getPath() + File.separator;
+
+            List<ProjectFile> files = project.buildFileList();
+            for (ProjectFile pr : files)
+                addFile(projectPath + pr.getLocation());
+        }
+
+        resumeWatching();
+    }
+
+    @Override
+    protected void doOnChange(FileInfo fileInfo) {
+        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) {
+                ((OpenProjectAction) Application.getInstance().getAction(
+                        OpenProjectAction.getActionName())).openProject(mediator
+                        .getProject()
+                        .getMainFile());
+            }
+
+        }
+    }
+
+    @Override
+    protected void doOnRemove(FileInfo fileInfo) {
+        if (mediator.getProject() != null
+                && fileInfo.getFile().equals(mediator.getProject().getMainFile()))
{
+            FileDeletedDialog dialog = new FileDeletedDialog(Application.getFrame());
+            dialog.show();
+
+            if (dialog.shouldSave()) {
+                Application
+                        .getInstance()
+                        .getAction(SaveAsAction.getActionName())
+                        .performAction(null);
+            }
+            else if (dialog.shouldClose()) {
+                CayenneModelerController controller = Application
+                        .getInstance()
+                        .getFrameController();
+
+                controller.projectClosedAction();
+            }
+        }
+        else
+            ;// pretend that nothing happened by now
+    }
+
+    /**
+     * 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);
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SaveAsAction.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SaveAsAction.java?rev=656473&r1=656472&r2=656473&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SaveAsAction.java
(original)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/SaveAsAction.java
Wed May 14 17:33:42 2008
@@ -59,9 +59,10 @@
     }
 
     public KeyStroke getAcceleratorKey() {
-        return KeyStroke.getKeyStroke
-                 (KeyEvent.VK_S,
-                  Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | ActionEvent.SHIFT_MASK);
+        return KeyStroke.getKeyStroke(KeyEvent.VK_S, Toolkit
+                .getDefaultToolkit()
+                .getMenuShortcutKeyMask()
+                | ActionEvent.SHIFT_MASK);
     }
 
     /**
@@ -78,6 +79,8 @@
             return false;
         }
 
+        getProjectController().getProjectWatcher().pauseWatching();
+
         p.save();
 
         // update preferences domain key
@@ -85,6 +88,12 @@
 
         getApplication().getFrameController().addToLastProjListAction(
                 p.getMainFile().getAbsolutePath());
+
+        /**
+         * Reset the watcher now
+         */
+        getProjectController().getProjectWatcher().reconfigure();
+
         return true;
     }
 

Added: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/FileDeletedDialog.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/FileDeletedDialog.java?rev=656473&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/FileDeletedDialog.java
(added)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/FileDeletedDialog.java
Wed May 14 17:33:42 2008
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   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.dialog;
+
+import java.awt.Component;
+
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+
+/**
+ * A dialog to show if cayenne.xml was renamed or deleted by other program
+ */
+public class FileDeletedDialog {
+
+    private static final String SAVE = "Save Changes";
+    private static final String CLOSE = "Close Project";
+    private static final String CANCEL = "Cancel";
+
+    private static final String[] OPTIONS = new String[] {
+            SAVE, CLOSE, CANCEL
+    };
+
+    protected Component parent;
+    protected String result = CANCEL;
+
+    public FileDeletedDialog(Component parent) {
+        this.parent = parent;
+    }
+
+    public void show() {
+        JOptionPane pane = new JOptionPane(
+                "Main project file was deleted or renamed. "
+                        + "Do you want to save the changes or close the project?",
+                JOptionPane.QUESTION_MESSAGE);
+        pane.setOptions(OPTIONS);
+
+        JDialog dialog = pane.createDialog(parent, "File deleted");
+        dialog.setVisible(true);
+
+        Object selectedValue = pane.getValue();
+        // need to do an if..else chain, since
+        // sometimes values are unexpected
+        if (SAVE.equals(selectedValue)) {
+            result = SAVE;
+        }
+        else if (CLOSE.equals(selectedValue)) {
+            result = CLOSE;
+        }
+        else {
+            result = CANCEL;
+        }
+    }
+
+    public boolean shouldSave() {
+        return SAVE.equals(result);
+    }
+
+    public boolean shouldClose() {
+        return CLOSE.equals(result);
+    }
+
+    public boolean shouldCancel() {
+        return result == null || CANCEL.equals(result);
+    }
+}

Added: cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/FileWatchdog.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/FileWatchdog.java?rev=656473&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/FileWatchdog.java
(added)
+++ cayenne/main/trunk/framework/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/util/FileWatchdog.java
Wed May 14 17:33:42 2008
@@ -0,0 +1,273 @@
+/*
+ * 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.util;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * FileWatchdog is a watcher for files' change. If one of the files has changed or been
+ * removed, a {@link #doOnChange(org.apache.cayenne.modeler.util.FileWatchdog.FileInfo)}
+ * or {@link #doOnRemove(org.apache.cayenne.modeler.util.FileWatchdog.FileInfo) method}
+ * will be called
+ * 
+ * Original code taken from Log4J project
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Andrey Razumovsky
+ */
+public abstract class FileWatchdog extends Thread {
+
+    /**
+     * The default delay between every file modification check
+     */
+    static final public long DEFAULT_DELAY = 4000;
+
+    /**
+     * The names of the files to observe for changes.
+     */
+    protected Map<String, FileInfo> filesInfo;
+
+    /**
+     * The delay to observe between every check. By default set {@link #DEFAULT_DELAY}.
+     */
+    protected long delay = DEFAULT_DELAY;
+
+    /**
+     * Paused flag
+     */
+    protected boolean paused;
+
+    /**
+     * This flags shows whether only one or multiple notifications will be fired when
+     * several files change
+     */
+    protected boolean singleNotification;
+
+    /**
+     * An object to enable synchronization
+     */
+    private Object sync = new Object();
+
+    private static Log log = LogFactory.getLog(FileWatchdog.class);
+
+    protected FileWatchdog() {
+        filesInfo = Collections.synchronizedMap(new HashMap<String, FileInfo>());
+        setDaemon(true);
+    }
+
+    /**
+     * Sets whether only one or multiple notifications will be fired when several files
+     * change
+     */
+    public void setSingleNotification(boolean b) {
+        singleNotification = b;
+    }
+
+    /**
+     * Returns whether only one or multiple notifications will be fired when several files
+     * change
+     */
+    public boolean isSingleNotification() {
+        return singleNotification;
+    }
+
+    /**
+     * Adds a new file to watch
+     * 
+     * @param location path of file
+     */
+    public void addFile(String location) {
+        synchronized (sync) {
+            try {
+                filesInfo.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) {
+        synchronized (sync) {
+            filesInfo.remove(location);
+        }
+    }
+
+    /**
+     * Turns off watching for all files
+     */
+    public void removeAllFiles() {
+        synchronized (sync) {
+            filesInfo.clear();
+        }
+    }
+
+    /**
+     * Set the delay to observe between each check of the file changes.
+     */
+    public void setDelay(long delay) {
+        this.delay = delay;
+    }
+
+    /**
+     * Invoked when one of the watched files has changed
+     * 
+     * @param fileInfo Changed file info
+     */
+    protected abstract void doOnChange(FileInfo fileInfo);
+
+    /**
+     * Invoked when one of the watched files has been removed
+     * 
+     * @param fileInfo Changed file info
+     */
+    protected abstract void doOnRemove(FileInfo fileInfo);
+
+    protected void check() {
+        synchronized (sync) {
+            if (paused)
+                return;
+
+            List<FileInfo> changed = new Vector<FileInfo>();
+            List<FileInfo> deleted = new Vector<FileInfo>();
+
+            for (Iterator<FileInfo> it = filesInfo.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) {
+                    long l = fi.getFile().lastModified(); // this can also throw a
+                                                            // SecurityException
+                    if (l > fi.getLastModified()) { // however, if we reached this point
+                                                    // this
+                        fi.setLastModified(l); // is very unlikely.
+                        changed.add(fi);
+                    }
+                }
+                else if (fi.getLastModified() != -1) // the file has been removed
+                {
+                    deleted.add(fi);
+                    it.remove(); // no point to watch the file now
+                }
+            }
+
+            for (int i = 0; i < deleted.size(); i++) {
+                doOnRemove(deleted.get(i));
+                if (singleNotification)
+                    return;
+            }
+            for (int i = 0; i < changed.size(); i++) {
+                doOnChange(changed.get(i));
+                if (singleNotification)
+                    return;
+            }
+        }
+    }
+
+    public void run() {
+        while (true) {
+            try {
+                Thread.sleep(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() {
+        synchronized (sync) {
+            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;
+        }
+    }
+}



Mime
View raw message