/******************************************************************************* * Copyright (c) 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.bpel.common.ui.editmodel; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.bpel.model.resource.BPELResourceSetImpl; import org.eclipse.bpel.common.ui.CommonUIPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.ui.actions.WorkspaceModifyOperation; /** * EditModel is shared between editor with the same primary file input. It holds * on to a command stack, and a resource set. * * This class has a reference count cache of EditModels. Once all editors that * reference a editModel are closed all resource in the shared resource set are * unloaded. * * Life-cycle call getEditModel using its primary file. the shared resource set * reference count is incremented; call getResourceInfo using any file that need * to be loaded the shared resource reference count is incremented; call * releaseReference the shared resource reference count is decremented; call * release if it is the last reference the shared resource set is disposed */ public class EditModel { protected static EditModelCache cache = new EditModelCache(); protected Map<IFile,ResourceInfo> fileToResourceInfo = new HashMap<IFile,ResourceInfo>(); protected ResourceSet resourceSet; protected int referenceCount = 0; protected IResource primaryFile; protected EditModelCommandStack commandStack; private List<IEditModelListener> updateListeners = null; /** * Get the edit model based on the primary resource file. * * @param primaryFile * @return the edit model */ public static EditModel getEditModel(IResource primaryFile) { return cache.getEditModel(primaryFile, new Factory()); } /** * Get the edit model based on the primary resource file. * @param primaryFile primary file (the BPEL resource) * @param factory factory * @return the editor model * */ public static EditModel getEditModel(IResource primaryFile, Factory factory) { return cache.getEditModel(primaryFile, factory); } /** * Private constructor. Use the static factory methods. */ protected EditModel(ResourceSet rSet, IResource bpelFile) { this.resourceSet = rSet; this.primaryFile = bpelFile; this.updateListeners = new ArrayList<IEditModelListener>(); } /** * Add a model listener. * @param listener */ public void addListener (IEditModelListener listener) { if (updateListeners.contains(listener)) { return; } updateListeners.add(listener); } /** * Remove a model listener. * @param listener */ public void removeListener(IEditModelListener listener) { updateListeners.remove(listener); } protected void fireModelDirtyStateChanged(ResourceInfo sr) { for (IEditModelListener next : updateListeners ) { next.modelDirtyStateChanged(sr); } } protected void fireModelDeleted(ResourceInfo sr) { // bugzilla 324006 // Prevent concurrent list modification exception: // When a resource is deleted, the BPEL editor shuts itself down for no // apparently good reason (I'm sure it's to avoid some other kinds of disasters // that it isn't equipped to deal with!) This causes the editor to remove itself // from our update listeners list. ArrayList<IEditModelListener> listeners = new ArrayList<IEditModelListener>(updateListeners.size()); listeners.addAll(updateListeners); for (IEditModelListener next : listeners ) { next.modelDeleted(sr); } } protected void fireModelReloaded(ResourceInfo sr) { ArrayList<IEditModelListener> listeners = new ArrayList<IEditModelListener>(updateListeners.size()); listeners.addAll(updateListeners); for (IEditModelListener next : listeners ) { next.modelReloaded(sr); } } protected void fireModelLocationChanged(ResourceInfo sr, IFile movedToFile) { ArrayList<IEditModelListener> listeners = new ArrayList<IEditModelListener>(updateListeners.size()); listeners.addAll(updateListeners); for (IEditModelListener next : listeners ) { next.modelLocationChanged(sr,movedToFile); } } protected void fireModelMarkersChanged (ResourceInfo sr, IMarkerDelta[] markerDelta ) { ArrayList<IEditModelListener> listeners = new ArrayList<IEditModelListener>(updateListeners.size()); listeners.addAll(updateListeners); for (IEditModelListener next : listeners ) { next.modelMarkersChanged(sr,markerDelta ); } } /** * Return the resource set associated with this editModel. * @return the resource set used by this Edit Model client. */ public ResourceSet getResourceSet() { return resourceSet; } /** * Returns the cached ResourceInfo for <code>file</code> * * Creates a new ResourceInfo it is not found in the cache, otherwise * increment the reference count and return it. */ ResourceInfo getResourceInfoForLoadedResource(Resource resource) { URI uri = resource.getURI(); IFile file = getIFileForURI(uri); if (file == null) return null; ResourceInfo resourceInfo = fileToResourceInfo.get(file); if (resourceInfo == null) { resourceInfo = new ResourceInfo(this, file); resourceInfo.setResource(resource); addResourceInfo(resourceInfo); resourceInfo.resourceLoaded(); } if (!resourceInfo.loading) resourceInfo.referenceCount++; return resourceInfo; } /** * Returns the cached ResourceInfo for <code>file</code> * * Creates a new ResourceInfo it is not found in the cache, otherwise * increment the reference count and return it. * * @param file the file * @return the cached resource info for that file. */ public ResourceInfo getResourceInfo(IFile file) { ResourceInfo resourceInfo = fileToResourceInfo.get(file); if (resourceInfo == null) { resourceInfo = new ResourceInfo(this, file); addResourceInfo(resourceInfo); try { resourceInfo.load(); } catch (RuntimeException ex) { resourceInfo.referenceCount++; releaseReference(resourceInfo); throw ex; } } resourceInfo.referenceCount++; return resourceInfo; } static ResourceInfo [] EMPTY_RESOURCE_ARRAY = {}; /** * * @return The resource infos * */ public ResourceInfo[] getResourceInfos() { return fileToResourceInfo.values().toArray(EMPTY_RESOURCE_ARRAY); } private void setPrimaryFile(IFile newFile) { IResource oldFile = primaryFile; primaryFile = newFile; cache.updatePrimaryFile(oldFile, newFile); } /** * @return the primary resource (primary file) for this edit model. */ public IResource getPrimaryFile() { return primaryFile; } /** * Decrement the reference count for <code>resourceInfo</code> and dispose * if it is the last reference. * @param resourceInfo */ public void releaseReference (ResourceInfo resourceInfo) { resourceInfo.referenceCount--; if (resourceInfo.referenceCount == 0) { resourceInfo.dispose(); removeResourceInfo(resourceInfo); } } /** * Add resourceInfo to the cache */ protected void addResourceInfo(ResourceInfo sr) { fileToResourceInfo.put(sr.getFile(), sr); } /* * Remove the resourceInfo from the cache. */ protected void removeResourceInfo(ResourceInfo sr) { fileToResourceInfo.remove(sr.getFile()); } /** * Dispose this EditModel if there is no other reference to it; */ public void release() { referenceCount--; if (referenceCount == 0) { cache.remove(this); for (ResourceInfo resourceInfo : fileToResourceInfo.values().toArray(EMPTY_RESOURCE_ARRAY) ) { resourceInfo.dispose(); } fileToResourceInfo.clear(); } } public static IFile getIFileForURI(URI uri) { String filePath = null; String scheme = uri.scheme(); IFile file = null; if ("file".equals(scheme)) { //$NON-NLS-1$ filePath = uri.toFileString(); if (filePath == null) return null; file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation( new Path(filePath)); } else if ("platform".equals(scheme) && uri.segmentCount() > 1 && "resource".equals(uri.segment(0))) { //$NON-NLS-1$//$NON-NLS-2$ StringBuffer platformResourcePath = new StringBuffer(); for (int i = 1, size = uri.segmentCount(); i < size; ++i) { platformResourcePath.append('/'); platformResourcePath.append(uri.segment(i)); } filePath = platformResourcePath.toString(); if (filePath == null) return null; file = ResourcesPlugin.getWorkspace().getRoot().getFile( new Path(filePath)); } return file; } /** * Get the command stack. * * @return the command stack. */ public EditModelCommandStack getCommandStack() { return commandStack; } /** * Set the command stack. * * @param stack the command stack. */ public void setCommandStack(EditModelCommandStack stack) { this.commandStack = stack; } /** * Saves the model * * @param progressMonitor progress monitor. * @return if all went OK true, false otherwise. */ public boolean saveAll(IProgressMonitor progressMonitor) { WorkspaceModifyOperation operation = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws InvocationTargetException { getCommandStack().markSaveLocation(); try { ResourceInfo[] sResource = getResourceInfos(); for (int i = 0; i < sResource.length; i++) { ResourceInfo resource = sResource[i]; if (resource.isDirty()) resource.save(); } } catch (IOException e) { throw new InvocationTargetException(e); } } }; try { operation.run(progressMonitor); } catch (InvocationTargetException e) { return false; } catch (InterruptedException e) { return false; } return true; } /** * Save primary resource as ... * * @param resourceInfo * @param savedFile * @param progressMonitor * @return save result (true is OK). */ public boolean savePrimaryResourceAs(final ResourceInfo resourceInfo, final IFile savedFile, IProgressMonitor progressMonitor) { boolean result = saveResourceAs(resourceInfo, savedFile, progressMonitor); if (result) setPrimaryFile(savedFile); return result; } /** * Save resource as ... * @param resourceInfo * @param savedFile * @param progressMonitor * @return the status of the save ... */ public boolean saveResourceAs(final ResourceInfo resourceInfo, final IFile savedFile, IProgressMonitor progressMonitor) { WorkspaceModifyOperation operation = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws InvocationTargetException { try { getCommandStack().markSaveLocation(); resourceInfo.saveAs(savedFile); } catch (IOException e) { throw new InvocationTargetException(e); } } }; try { operation.run(progressMonitor); } catch (InvocationTargetException e) { return false; } catch (InterruptedException e) { return false; } return true; } /** * * @author IBM * */ public static class Factory { protected EditModel createEditModel(ResourceSet resourceSet, IResource primaryFile) { return new EditModel(resourceSet, primaryFile); } } static class EditModelCache { protected Map<ResourceSet,EditModel> resourceSetToEditModel = new HashMap<ResourceSet,EditModel>(); protected Map<IResource,ResourceSet> fileToResourceSet = new HashMap<IResource,ResourceSet>(); /** * Return a new ResourceSet for the specified file. * @param primaryFile * @param factory * @return the edit model. */ public EditModel getEditModel(IResource primaryFile, Factory factory) { ResourceSet resourceSet = getResourceSet(primaryFile); return getEditModel(resourceSet, primaryFile, factory); } /** * Return the EditModel for specified resource set. * * Creates a new EditModel it is not found in the cache, otherwise * increment the reference count and return it. */ @SuppressWarnings("unchecked") private EditModel getEditModel(ResourceSet resourceSet, IResource primaryFile, Factory factory) { EditModel editModel = resourceSetToEditModel.get(resourceSet); if (editModel != null) { editModel.referenceCount++; return editModel; } editModel = factory.createEditModel(resourceSet, primaryFile); editModel.referenceCount++; resourceSetToEditModel.put(resourceSet, editModel); final EditModel finalEditModel = editModel; resourceSet.eAdapters().add(new AdapterImpl() { @Override public void notifyChanged(Notification msg) { Resource r = (Resource) msg.getNewValue(); finalEditModel.getResourceInfoForLoadedResource(r); } }); return editModel; } /* * Return a new ResourceSet for the specified file. */ private ResourceSet getResourceSet(IResource primaryFile) { ResourceSet resourceSet = fileToResourceSet.get(primaryFile); if(resourceSet != null) return resourceSet; // TODO: Extensibility resourceSet = new BPELResourceSetImpl(); fileToResourceSet.put(primaryFile,resourceSet); return resourceSet; } /** * * @param editModel */ public void remove(EditModel editModel) { resourceSetToEditModel.remove(editModel.resourceSet); fileToResourceSet.remove(editModel.primaryFile); } void updatePrimaryFile(IResource oldFile, IResource newFile) { ResourceSet rs = fileToResourceSet.get(oldFile); fileToResourceSet.remove(oldFile); fileToResourceSet.put(newFile, rs); } } static String EXTPT_RESOURCE_SET_PROVIDER = "resourceSetProvider"; //$NON-NLS-1$ static String ELM_PROVIDER = "provider"; //$NON-NLS-1$ static String ATT_CLASS = "class"; //$NON-NLS-1$ static private IResourceSetProvider gfResourceSetProvider; static IResourceSetProvider getResourceSetProvider () { if (gfResourceSetProvider != null) { return gfResourceSetProvider; } for(IConfigurationElement elm : CommonUIPlugin.getConfigurationElements( EXTPT_RESOURCE_SET_PROVIDER)) { if (ELM_PROVIDER.equals(elm.getName()) == false) { continue; } String clazz = elm.getAttribute(ATT_CLASS); if (clazz != null) { try { gfResourceSetProvider = (IResourceSetProvider) elm.createExecutableExtension(ATT_CLASS); } catch ( CoreException ce ) { CommonUIPlugin.getDefault().getLog().log(ce.getStatus()); } } if (gfResourceSetProvider != null) { break; } } if (gfResourceSetProvider == null) { gfResourceSetProvider = new IResourceSetProvider () { public ResourceSet getResourceSet (IResource resource) { return new ResourceSetImpl(); } }; } return gfResourceSetProvider; } }