
org.apache.cayenne.modeler.ProjectFileChangeTracker Maven / Gradle / Ivy
/*****************************************************************
* 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
*
* https://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.net.URI;
import java.net.URISyntaxException;
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.CayenneRuntimeException;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* ProjectWatchdog class is responsible for tracking changes in cayenne.xml and
* other Cayenne project files
*
*/
public class ProjectFileChangeTracker extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(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 final Map files;
protected final ProjectController mediator;
protected boolean paused;
protected boolean isShownChangeDialog;
protected boolean isShownRemoveDialog;
public ProjectFileChangeTracker(ProjectController mediator) {
this.files = new ConcurrentHashMap<>();
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) {
try {
addFile(project.getConfigurationResource().getURL().toURI());
for (DataMap dm : ((DataChannelDescriptor) project.getRootNode()).getDataMaps()) {
if (dm.getConfigurationSource() != null) {
// if DataMap is in separate file, monitor it
addFile(dm.getConfigurationSource().getURL().toURI());
}
}
} catch (URISyntaxException ex) {
throw new CayenneRuntimeException("Unable to start change tracker", ex);
}
}
resumeWatching();
}
protected void doOnChange() {
SwingUtilities.invokeLater(() -> {
isShownChangeDialog = true;
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;
try {
fileDirectory = new File(mediator.getProject().getConfigurationResource().getURL().toURI());
} catch (URISyntaxException e) {
throw new CayenneRuntimeException("Unable to open project %s",
e, mediator.getProject().getConfigurationResource().getURL());
}
Application.getInstance().getActionManager()
.getAction(OpenProjectAction.class)
.openProject(fileDirectory);
}
} else {
mediator.setDirty(true);
}
isShownChangeDialog = false;
});
}
protected void doOnRemove() {
if (mediator.getProject() != null) {
SwingUtilities.invokeLater(() -> {
isShownRemoveDialog = true;
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);
}
isShownRemoveDialog = false;
});
}
}
/**
* 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(URI location) {
try {
files.put(location, new FileInfo(location));
} catch (SecurityException e) {
LOGGER.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(URI.create(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 it = files.values().iterator(); it.hasNext();) {
FileInfo fi = it.next();
boolean fileExists;
try {
fileExists = fi.getFile().exists();
} catch (SecurityException e) {
LOGGER.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;
}
} else if (fi.getLastModified() != -1) {
// the file has been removed
hasDeletions = true;
it.remove(); // no point to watch the file now
}
}
if (hasDeletions && !isShownRemoveDialog) {
doOnRemove();
} else if (hasChanges && !isShownChangeDialog) {
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 static class FileInfo {
/**
* Exact java.io.File object, may not be null
*/
private final File file;
/**
* Time the file was modified
*/
private long lastModified;
/**
* Creates new object
*
* @param location
* the file path
*/
protected FileInfo(URI location) {
file = new File(location);
lastModified = file.exists() ? file.lastModified() : -1;
}
protected File getFile() {
return file;
}
protected long getLastModified() {
return lastModified;
}
protected void setLastModified(long l) {
lastModified = l;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy