package org.eclipse.emf.emfstore.client.model.impl; import java.io.File; import java.io.IOException; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.BasicEMap; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.xmi.XMIResource; import org.eclipse.emf.emfstore.client.model.Configuration; import org.eclipse.emf.emfstore.client.model.changeTracking.commands.CommandObserver; import org.eclipse.emf.emfstore.client.model.changeTracking.commands.EMFStoreCommandStack; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.NotificationInfo; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.filter.EmptyRemovalsFilter; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.filter.FilterStack; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.filter.NotificationFilter; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.filter.TouchFilter; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.filter.TransientFilter; import org.eclipse.emf.emfstore.client.model.util.WorkspaceUtil; import org.eclipse.emf.emfstore.common.model.IdEObjectCollection; import org.eclipse.emf.emfstore.common.model.ModelElementId; import org.eclipse.emf.emfstore.common.model.util.EObjectChangeNotifier; import org.eclipse.emf.emfstore.common.model.util.IdEObjectCollectionChangeObserver; import org.eclipse.emf.emfstore.common.model.util.ModelUtil; public class StatePersister implements CommandObserver, IdEObjectCollectionChangeObserver { private IdEObjectCollection collection; /** * Set containing all dirty resources. */ private DirtyResourceSet dirtyResourceSet; /** * Indicates whether a command is running. */ private boolean commandIsRunning; /** * Indicates whether a resource is saved automatically. */ private boolean autoSave; /** * Indicates whether a resource may be split when a model element has been * added. */ private boolean splitResource; private EMFStoreCommandStack commandStack; private FilterStack filterStack; // TODO: 2nd parameter, commandstack // change notifier per methode setzen public StatePersister(EObjectChangeNotifier changeNotifier, EMFStoreCommandStack commandStack, IdEObjectCollection collection) { this.autoSave = true; this.commandStack = commandStack; this.collection = collection; this.commandStack.addCommandStackObserver(this); this.dirtyResourceSet = new DirtyResourceSet(); filterStack = new FilterStack(new NotificationFilter[] { new TouchFilter(), new TransientFilter(), new EmptyRemovalsFilter() }); } public void commandStarted(Command command) { commandIsRunning = true; } public void commandCompleted(Command command) { commandIsRunning = false; saveDirtyResources(); } public void commandFailed(Command command, Exception exception) { commandIsRunning = false; } private void cleanResources(EObject deletedElement) { Resource resource = deletedElement.eResource(); if (resource != null) { resource.getContents().remove(deletedElement); dirtyResourceSet.addDirtyResource(resource); } for (EObject child : ModelUtil.getAllContainedModelElements(deletedElement, false)) { Resource childResource = child.eResource(); if (childResource != null) { childResource.getContents().remove(child); dirtyResourceSet.addDirtyResource(childResource); } } } /** * Adds the given model element's resource to the set of dirty resources. * * @param modelElement * the model element */ private void addToDirtyResources(EObject modelElement) { Resource resource = modelElement.eResource(); if (resource != null) { dirtyResourceSet.addDirtyResource(resource); setModelElementIdAndChildrenIdOnResource((XMIResource) resource, modelElement); } } /** * Save all dirty resources to disk now if autosave is active. */ public void saveDirtyResources() { if (autoSave) { dirtyResourceSet.save(); } } public void notify(Notification notification, IdEObjectCollection rootEObject, EObject modelElement) { // filter unwanted notifications that did not change anything in the // state if (filterStack.check(new NotificationInfo(notification))) { return; } addToDirtyResources(modelElement); if (!commandIsRunning) { saveDirtyResources(); } } public void modelElementAdded(IdEObjectCollection rootEObject, EObject modelElement) { XMIResource oldResource = (XMIResource) modelElement.eResource(); // do not split if splitting disabled or the element is a map entry if (oldResource != null && Configuration.isResourceSplittingEnabled() && !(modelElement instanceof BasicEMap.Entry)) { addToNewResourceIfRequired(modelElement, oldResource); } addToDirtyResources(modelElement); } public void modelElementRemoved(IdEObjectCollection rootEObject, EObject modelElement) { // TODO: put // ModelUtil.removeModelElementAndChildrenFromResource(modelElement); // and unsetModelElementAnd.. into one place cleanResources(modelElement); } public void collectionDeleted(IdEObjectCollection collection) { if (commandStack != null) { commandStack.removeCommandStackObserver(this); } } private static Resource currentResource; private void addToNewResourceIfRequired(final EObject modelElement, XMIResource oldResource) { if (currentResource == null || currentResource.getURI() == null) { currentResource = oldResource; } URI oldUri = currentResource.getURI(); String oldFileName = oldUri.toFileString(); if (!oldUri.isFile()) { throw new IllegalStateException("Project contains ModelElements that are not part of a file resource."); } File oldFile = new File(oldFileName); if (oldFile.length() > Configuration.getMaxResourceFileSizeOnExpand()) { String newFileName; try { newFileName = File.createTempFile("frag", Configuration.getProjectFragmentFileExtension(), new File(oldFile.getParent())).getAbsolutePath(); } catch (IOException e) { // TODO: reasonable error message throw new IllegalStateException("File fragment \"" + "\" already exists - ProjectSpace corrupted."); } URI fileURI = URI.createFileURI(newFileName); XMIResource newResource = (XMIResource) currentResource.getResourceSet().createResource(fileURI); newResource.getContents().add(modelElement); setModelElementIdAndChildrenIdOnResource(newResource, modelElement); currentResource = newResource; } } private void setModelElementIdAndChildrenIdOnResource(XMIResource resource, EObject modelElement) { if (modelElement instanceof IdEObjectCollection) { return; } ModelElementId modelElementId = collection.getModelElementId(modelElement); if (modelElementId == null) { modelElementId = collection.getDeletedModelElementId(modelElement); } String modelElementIdString = modelElementId.getId(); resource.setID(modelElement, modelElementIdString); TreeIterator<EObject> it = modelElement.eAllContents(); while (it.hasNext()) { EObject child = it.next(); ModelElementId childId = getIDForEObject(child); if (childId == null) { childId = collection.getDeletedModelElementId(child); } resource.setID(child, childId.getId()); } } private ModelElementId getIDForEObject(EObject modelElement) { ModelElementId modelElementId = collection.getModelElementId(modelElement); if (modelElementId == null) { modelElementId = collection.getDeletedModelElementId(modelElement); } if (modelElementId == null) { WorkspaceUtil.handleException(new IllegalStateException("No ID for model element" + modelElement)); } return modelElementId; } /** * Enable or disable save. I save is disabled, dirty resources will not bes * saved. * * @param newValue * true if auto save should be enabled */ public void setAutoSave(boolean newValue) { autoSave = newValue; } /** * Sets whether a resource split may occur when a model element is added. * * @param splitResource * whether resource splitting should occur */ public void setSplitResource(boolean splitResource) { this.splitResource = splitResource; } /** * Determines whether resource splitting is enabled. * * @return true, if resource splitting may occur */ public boolean isSplitResource() { return splitResource; } }