/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.module;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.ApplicationContext;
import org.openflexo.FlexoCst;
import org.openflexo.GeneralPreferences;
import org.openflexo.components.AskParametersDialog;
import org.openflexo.components.ProgressWindow;
import org.openflexo.foundation.FlexoEditor;
import org.openflexo.foundation.param.CheckboxParameter;
import org.openflexo.foundation.param.DirectoryParameter;
import org.openflexo.foundation.param.DynamicDropDownParameter;
import org.openflexo.foundation.param.FileParameter;
import org.openflexo.foundation.param.ParametersModel;
import org.openflexo.foundation.rm.FlexoProject;
import org.openflexo.foundation.rm.FlexoProjectReference;
import org.openflexo.foundation.rm.FlexoResourceManager;
import org.openflexo.foundation.rm.SaveResourceException;
import org.openflexo.foundation.rm.SaveResourceExceptionList;
import org.openflexo.foundation.rm.SaveResourcePermissionDeniedException;
import org.openflexo.foundation.utils.FlexoProjectUtil;
import org.openflexo.foundation.utils.ProjectInitializerException;
import org.openflexo.foundation.utils.ProjectLoadingCancelledException;
import org.openflexo.foundation.utils.UnreadableProjectException;
import org.openflexo.foundation.xml.FlexoXMLMappings;
import org.openflexo.inspector.widget.FileEditWidget;
import org.openflexo.localization.FlexoLocalization;
import org.openflexo.model.exceptions.ModelDefinitionException;
import org.openflexo.model.factory.ModelFactory;
import org.openflexo.prefs.FlexoPreferences;
import org.openflexo.toolbox.FileUtils;
import org.openflexo.toolbox.FlexoVersion;
import org.openflexo.toolbox.HasPropertyChangeSupport;
import org.openflexo.view.controller.FlexoController;
import org.openflexo.view.controller.InteractiveFlexoEditor;
public class ProjectLoader implements HasPropertyChangeSupport {
public static final String PROJECT_OPENED = "projectOpened";
public static final String PROJECT_CLOSED = "projectClosed";
private static final Logger logger = Logger.getLogger(ModuleLoader.class.getPackage().getName());
private static final String FOR_FLEXO_SERVER = "_forFlexoServer_";
public static final String EDITOR_ADDED = "editorAdded";
public static final String EDITOR_REMOVED = "editorRemoved";
public static final String ROOT_PROJECTS = "rootProjects";
private InteractiveFlexoResourceUpdateHandler resourceUpdateHandler;
private final ApplicationContext applicationContext;
private Map<FlexoProject, FlexoEditor> editors;
private Map<FlexoProject, AutoSaveService> autoSaveServices;
private PropertyChangeSupport propertyChangeSupport;
private List<FlexoProject> rootProjects;
private ModelFactory modelFactory;
public ProjectLoader(ApplicationContext applicationContext) throws ModelDefinitionException {
this.rootProjects = new ArrayList<FlexoProject>();
this.applicationContext = applicationContext;
this.editors = new LinkedHashMap<FlexoProject, FlexoEditor>();
this.propertyChangeSupport = new PropertyChangeSupport(this);
autoSaveServices = new HashMap<FlexoProject, AutoSaveService>();
modelFactory = new ModelFactory(FlexoProjectReference.class);
}
@Override
public PropertyChangeSupport getPropertyChangeSupport() {
return propertyChangeSupport;
}
@Override
public String getDeletedProperty() {
// TODO Auto-generated method stub
return null;
}
public FlexoEditor getEditorForProject(FlexoProject project) {
return editors.get(project);
}
public FlexoEditor editorForProjectURIAndRevision(String projectURI, long revision) {
for (Entry<FlexoProject, FlexoEditor> e : editors.entrySet()) {
if (e.getKey().getProjectURI().equals(projectURI) && e.getKey().getRevision() == revision) {
return e.getValue();
}
}
return null;
}
public boolean hasEditorForProjectDirectory(File projectDirectory) {
if (projectDirectory == null) {
return false;
}
for (Entry<FlexoProject, FlexoEditor> e : editors.entrySet()) {
if (e.getKey().getProjectDirectory().equals(projectDirectory)) {
return true;
}
}
return false;
}
public FlexoEditor loadProject(File projectDirectory) throws ProjectLoadingCancelledException, ProjectInitializerException {
return loadProject(projectDirectory, false);
}
/**
* Loads the project located withing <code> projectDirectory </code>. The following method is the default methode to call when opening a
* project from a GUI (Interactive mode) so that resource update handling is properly initialized. Additional small stuffs can be
* performed in that call so that projects are always opened the same way.
*
* @param projectDirectory
* the project directory
* @return the {@link InteractiveFlexoEditor} editor if the opening succeeded else <code>null</code>
* @throws org.openflexo.foundation.utils.ProjectLoadingCancelledException
* whenever the load procedure is interrupted by the user or by Flexo.
* @throws ProjectInitializerException
*/
public FlexoEditor loadProject(File projectDirectory, boolean asImportedProject) throws ProjectLoadingCancelledException,
ProjectInitializerException {
if (projectDirectory == null) {
throw new IllegalArgumentException("Project directory cannot be null");
}
if (!projectDirectory.exists()) {
throw new ProjectInitializerException("project directory does not exist", projectDirectory);
}
try {
FlexoProjectUtil.isProjectOpenable(projectDirectory);
} catch (UnreadableProjectException e) {
FlexoController.notify(e.getMessage());
ProgressWindow.hideProgressWindow();
throw new ProjectLoadingCancelledException(e.getMessage());
}
if (ProgressWindow.hasInstance()) {
ProgressWindow.hideProgressWindow();
}
ProgressWindow.showProgressWindow(FlexoLocalization.localizedForKey("loading_project"), 14);
FlexoEditor editor = null;
try {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Opening " + projectDirectory.getAbsolutePath());
}
if (!asImportedProject) {
// Adds to recent project
preInitialization(projectDirectory);
}
for (Entry<FlexoProject, FlexoEditor> e : editors.entrySet()) {
if (e.getKey().getProjectDirectory().equals(projectDirectory)) {
editor = e.getValue();
}
}
if (editor == null) {
editor = FlexoResourceManager.initializeExistingProject(projectDirectory, ProgressWindow.instance(), applicationContext,
applicationContext.getProjectLoadingHandler(projectDirectory), applicationContext.getProjectReferenceLoader(),
applicationContext.getResourceCenterService());
newEditor(editor);
}
if (!asImportedProject) {
addToRootProjects(editor.getProject());
}
} finally {
ProgressWindow.hideProgressWindow();
}
ProgressWindow.hideProgressWindow();
return editor;
}
public void reloadProject(FlexoProject project) throws ProjectLoadingCancelledException, ProjectInitializerException {
File projectDirectory = project.getProjectDirectory();
closeProject(project);
loadProject(projectDirectory);
}
public FlexoEditor newProject(File projectDirectory) {
if (!ProgressWindow.hasInstance()) {
ProgressWindow.showProgressWindow(FlexoLocalization.localizedForKey("building_new_project"), 10);
} else {
ProgressWindow.setProgressInstance(FlexoLocalization.localizedForKey("building_new_project"));
}
try {
// This will just create the .version in the project
FlexoProjectUtil.currentFlexoVersionIsSmallerThanLastVersion(projectDirectory);
preInitialization(projectDirectory);
FlexoEditor editor = FlexoResourceManager.initializeNewProject(projectDirectory, ProgressWindow.instance(), applicationContext,
applicationContext.getProjectReferenceLoader(), applicationContext.getResourceCenterService());
newEditor(editor);
addToRootProjects(editor.getProject());
return editor;
} finally {
ProgressWindow.hideProgressWindow();
}
}
private void newEditor(FlexoEditor editor) {
editors.put(editor.getProject(), editor);
if (applicationContext.isAutoSaveServiceEnabled()) {
autoSaveServices.put(editor.getProject(), new AutoSaveService(this, editor.getProject()));
}
getPropertyChangeSupport().firePropertyChange(EDITOR_ADDED, null, editor);
try {
FlexoProjectReference ref = modelFactory.newInstance(FlexoProjectReference.class);
ref.init(editor.getProject());
applicationContext.getResourceCenterService().getUserResourceCenter()
.publishResource(ref, editor.getProject().getVersion(), null);
} catch (Exception e) {
e.printStackTrace();
}
for (FlexoProject project : new ArrayList<FlexoProject>(editors.keySet())) {
if (project.getProjectData() != null) {
for (FlexoProjectReference reference : project.getProjectData().getImportedProjects()) {
reference.getReferredProject(false);
}
}
}
editor.getProject().setModuleLoader(applicationContext.getModuleLoader());
}
public void closeProject(FlexoProject project) {
AutoSaveService autoSaveService = getAutoSaveService(project);
if (autoSaveService != null) {
autoSaveService.close();
autoSaveServices.remove(project);
}
FlexoEditor editor = editors.remove(project);
if (project != null) {
project.close();
removeFromRootProjects(project);
getPropertyChangeSupport().firePropertyChange(EDITOR_REMOVED, editor, null);
}
}
public AutoSaveService getAutoSaveService(FlexoProject project) {
return autoSaveServices.get(project);
}
public void saveProjectForServer(FlexoProject project) {
final String zipFilename = project.getProjectDirectory().getName()
.substring(0, project.getProjectDirectory().getName().length() - 4);
String zipFileNameProposal = zipFilename + FOR_FLEXO_SERVER + new FlexoVersion(1, 0, 0, -1, false, false);
File[] zips = project.getProjectDirectory().getParentFile().listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".zip") && pathname.getName().startsWith(zipFilename);
}
});
File previousVersion = null;
if (zips != null) {
for (File file : zips) {
if (previousVersion == null || previousVersion.lastModified() < file.lastModified()) {
previousVersion = file;
}
}
}
if (previousVersion != null && previousVersion.getName().indexOf(FOR_FLEXO_SERVER) > -1) {
String version = previousVersion.getName()
.substring(previousVersion.getName().indexOf(FOR_FLEXO_SERVER) + FOR_FLEXO_SERVER.length(),
previousVersion.getName().length() - 4);
if (FlexoVersion.isValidVersionString(version)) {
FlexoVersion v = new FlexoVersion(version);
v.minor++;
zipFileNameProposal = zipFilename + FOR_FLEXO_SERVER + v;
}
}
final FileParameter targetZippedProject = new FileParameter("targetZippedProject", "new_zip_file", new File(project
.getProjectDirectory().getParentFile(), zipFileNameProposal + ".zip")) {
@Override
public void setValue(File value) {
if (!value.getName().endsWith(".zip")) {
value = new File(value.getParentFile(), value.getName() + ".zip");
}
super.setValue(value);
}
};
targetZippedProject.setDepends("targetZippedProject");
targetZippedProject.addParameter(FileEditWidget.TITLE, "select_a_zip_file");
targetZippedProject.addParameter(FileEditWidget.FILTER, "*.zip");
targetZippedProject.addParameter(FileEditWidget.MODE, FileEditWidget.SAVE);
CheckboxParameter removeScreenshotsAndLibraries = new CheckboxParameter("lighten", "remove_screenshots_and_libs", true);
AskParametersDialog dialog = AskParametersDialog.createAskParametersDialog(project, null,
FlexoLocalization.localizedForKey("save_project_as"), FlexoLocalization.localizedForKey("select_a_zip_file_for_project"),
new AskParametersDialog.ValidationCondition() {
@Override
public boolean isValid(ParametersModel model) {
if (targetZippedProject.getValue() == null) {
errorMessage = FlexoLocalization.localizedForKey("please_submit_a_zip");
return false;
}
return true;
}
}, targetZippedProject, removeScreenshotsAndLibraries);
System.setProperty("apple.awt.fileDialogForDirectories", "false");
if (dialog.getStatus() == AskParametersDialog.VALIDATE) {
File zipFile = targetZippedProject.getValue();
if (zipFile == null) {
return;
}
if (!zipFile.exists()) {
try {
FileUtils.createNewFile(zipFile);
} catch (IOException e1) {
e1.printStackTrace();
FlexoController.notify(FlexoLocalization.localizedForKey("could_not_save_permission_denied"));
return;
}
} else {
if (!FlexoController.confirm(FlexoLocalization.localizedForKey("file_already_exists.replace_it?"))) {
return;
}
}
if (!zipFile.canWrite()) {
FlexoController.notify(FlexoLocalization.localizedForKey("could_not_save_permission_denied"));
return;
}
try {
ProgressWindow.showProgressWindow(FlexoLocalization.localizedForKey("saving"), removeScreenshotsAndLibraries.getValue() ? 5
: 2);
project.saveAsZipFile(zipFile, ProgressWindow.instance(), removeScreenshotsAndLibraries.getValue(), true);
ProgressWindow.hideProgressWindow();
} catch (SaveResourceException e) {
e.printStackTrace();
ProgressWindow.hideProgressWindow();
FlexoController.notify(FlexoLocalization.localizedForKey("save_as_operation_failed"));
}
}
}
public void saveAsProject(FlexoProject project) {
project.getXmlMappings();
List<FlexoVersion> availableVersions = new ArrayList<FlexoVersion>(FlexoXMLMappings.getReleaseVersions());
Collections.sort(availableVersions, Collections.reverseOrder(FlexoVersion.comparator));
final DirectoryParameter targetPrjDirectory = new DirectoryParameter("targetPrjDirectory", "new_project_file", project
.getProjectDirectory().getParentFile()) {
@Override
public void setValue(File value) {
if (!value.getName().endsWith(".prj")) {
value = new File(value.getParentFile(), value.getName() + ".prj");
}
super.setValue(value);
}
};
targetPrjDirectory.setDepends("targetPrjDirectory");
targetPrjDirectory.addParameter(FileEditWidget.TITLE, "select_a_prj_directory");
targetPrjDirectory.addParameter(FileEditWidget.FILTER, "*.prj");
targetPrjDirectory.addParameter(FileEditWidget.MODE, FileEditWidget.SAVE);
final DynamicDropDownParameter<FlexoVersion> versionParam = new DynamicDropDownParameter<FlexoVersion>("version", "version",
availableVersions, availableVersions.get(0));
versionParam.setShowReset(false);
AskParametersDialog dialog = AskParametersDialog.createAskParametersDialog(project, null,
FlexoLocalization.localizedForKey("save_project_as"),
FlexoLocalization.localizedForKey("enter_parameters_for_project_saving"), new AskParametersDialog.ValidationCondition() {
@Override
public boolean isValid(ParametersModel model) {
if (versionParam.getValue() == null) {
errorMessage = FlexoLocalization.localizedForKey("please_submit_a_version");
return false;
}
if (targetPrjDirectory.getValue() == null) {
errorMessage = FlexoLocalization.localizedForKey("please_submit_a_prj_directory");
return false;
}
if (!(targetPrjDirectory.getValue().getName().endsWith(".prj") && !targetPrjDirectory.getValue().exists())) {
errorMessage = FlexoLocalization.localizedForKey("please_submit_a_valid_prj_directory");
return false;
}
return true;
}
}, targetPrjDirectory, versionParam);
System.setProperty("apple.awt.fileDialogForDirectories", "false");
if (dialog.getStatus() == AskParametersDialog.VALIDATE) {
File projectDirectory = targetPrjDirectory.getValue();
if (projectDirectory == null) {
return;
} else if (!projectDirectory.exists()) {
if (!projectDirectory.mkdirs()) {
FlexoController.notify(FlexoLocalization.localizedForKey("could_not_create_prj_directory"));
return;
}
}
if (!projectDirectory.canWrite()) {
FlexoController.notify(FlexoLocalization.localizedForKey("could_not_save_permission_denied"));
return;
}
try {
ProgressWindow.showProgressWindow(FlexoLocalization.localizedForKey("saving"), 1);
project.saveAs(projectDirectory, ProgressWindow.instance(),
FlexoCst.BUSINESS_APPLICATION_VERSION.equals(versionParam.getValue()) ? null : versionParam.getValue(), true, true);
GeneralPreferences.addToLastOpenedProjects(projectDirectory);
ProgressWindow.hideProgressWindow();
} catch (SaveResourceException e) {
e.printStackTrace();
ProgressWindow.hideProgressWindow();
FlexoController.notify(FlexoLocalization.localizedForKey("save_as_operation_failed"));
}
}
}
public List<FlexoProject> getModifiedProjects() {
List<FlexoProject> projects = new ArrayList<FlexoProject>(editors.size());
// 1. compute all modified projects
for (FlexoProject project : getRootProjects()) {
if (project.hasUnsaveStorageResources()) {
projects.add(project);
}
}
// 2. we now add all the projects that depend on a modified project
// to the list of modified projects (so that they also get saved
for (FlexoProject modifiedProject : new ArrayList<FlexoProject>(projects)) {
for (FlexoProject project : getRootProjects()) {
if (project.importsProject(modifiedProject)) {
if (!projects.contains(project)) {
projects.add(project);
}
}
}
}
// 3. We restore the order of projects according to the one in rootProjects
Collections.sort(projects, new Comparator<FlexoProject>() {
@Override
public int compare(FlexoProject o1, FlexoProject o2) {
return rootProjects.indexOf(o1) - rootProjects.indexOf(o2);
}
});
return projects;
}
public List<FlexoProject> getRootProjects() {
return rootProjects;
}
private void addToRootProjects(FlexoProject project) {
if (!rootProjects.contains(project)) {
rootProjects.add(project);
getPropertyChangeSupport().firePropertyChange(PROJECT_OPENED, null, project);
getPropertyChangeSupport().firePropertyChange(ROOT_PROJECTS, null, project);
}
}
private void removeFromRootProjects(FlexoProject project) {
rootProjects.remove(project);
getPropertyChangeSupport().firePropertyChange(PROJECT_CLOSED, project, null);
getPropertyChangeSupport().firePropertyChange(ROOT_PROJECTS, project, null);
}
public void saveAllProjects() throws SaveResourceExceptionList {
// Saves all projects. It is necessary to save all projects because during serialization, a project may increment its revision which
// in turn can modify project that import the former one.
List<FlexoProject> projects = new ArrayList<FlexoProject>(rootProjects);
saveProjects(projects);
}
public void saveProjects(List<FlexoProject> projects) throws SaveResourceExceptionList {
List<SaveResourceException> exceptions = new ArrayList<SaveResourceException>();
Collections.sort(projects, new Comparator<FlexoProject>() {
@Override
public int compare(FlexoProject o1, FlexoProject o2) {
if (o1.importsProject(o2)) {
return 1;
} else if (o2.importsProject(o1)) {
return -1;
}
return 0;
}
});
try {
ProgressWindow.showProgressWindow(FlexoLocalization.localizedForKey("saving"), projects.size());
for (FlexoProject project : projects) {
try {
ProgressWindow.setProgressInstance(FlexoLocalization.localizedForKey("saving") + " " + project.getDisplayName());
project.save(ProgressWindow.instance());
} catch (SaveResourceException e) {
e.printStackTrace();
exceptions.add(e);
}
}
if (exceptions.size() > 0) {
throw new SaveResourceExceptionList(exceptions);
}
} finally {
ProgressWindow.hideProgressWindow();
}
}
static void informUserAboutSaveResourceException(SaveResourceException e) {
if (e instanceof SaveResourcePermissionDeniedException) {
informUserAboutPermissionDeniedException((SaveResourcePermissionDeniedException) e);
} else {
FlexoController.showError(FlexoLocalization.localizedForKey("error_during_saving"));
}
logger.warning("Exception raised: " + e.getClass().getName() + ". See console for details.");
logger.warning(e.getMessage());
e.printStackTrace();
}
private static void informUserAboutPermissionDeniedException(SaveResourcePermissionDeniedException e) {
if (e.getFileResource().getFile().isDirectory()) {
FlexoController.showError(FlexoLocalization.localizedForKey("permission_denied"),
FlexoLocalization.localizedForKey("project_was_not_properly_saved_permission_denied_directory") + "\n"
+ e.getFileResource().getFile().getAbsolutePath());
} else {
FlexoController.showError(FlexoLocalization.localizedForKey("permission_denied"),
FlexoLocalization.localizedForKey("project_was_not_properly_saved_permission_denied_file") + "\n"
+ e.getFileResource().getFile().getAbsolutePath());
}
}
private void preInitialization(File projectDirectory) {
GeneralPreferences.addToLastOpenedProjects(projectDirectory);
FlexoPreferences.savePreferences(true);
}
public InteractiveFlexoResourceUpdateHandler getFlexoResourceUpdateHandler() {
return resourceUpdateHandler;
}
public boolean someProjectsAreModified() {
for (FlexoProject project : getRootProjects()) {
if (project.hasUnsaveStorageResources()) {
return true;
}
}
return false;
}
}