/***************************************************************************** * Copyright (c) 2009 Atos Origin. * * * 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: * <a href="mailto:thomas.szadel@atosorigin.com">Thomas Szadel</a> - Initial API and implementation * *****************************************************************************/ package org.eclipse.papyrus.infra.ui.resources.refactoring; import static org.eclipse.papyrus.infra.ui.resources.Activator.log; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.ECrossReferenceAdapter; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.transaction.RecordingCommand; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange; import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor; import org.eclipse.papyrus.infra.core.modelsetquery.IModelSetQueryAdapter; import org.eclipse.papyrus.infra.core.modelsetquery.ModelSetQuery; import org.eclipse.papyrus.infra.core.resource.ModelSet; import org.eclipse.papyrus.infra.core.resource.ModelsReader; import org.eclipse.papyrus.infra.core.resource.sasheditor.DiModelUtils; import org.eclipse.papyrus.infra.core.services.ServiceException; import org.eclipse.papyrus.infra.core.utils.DiResourceSet; import org.eclipse.papyrus.infra.core.utils.EditorUtils; import org.eclipse.papyrus.infra.services.controlmode.mm.history.ControledResource; import org.eclipse.papyrus.infra.services.controlmode.mm.history.historyPackage; import org.eclipse.papyrus.infra.ui.resources.Activator; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.part.FileEditorInput; /** * Rename the model.<BR> * <b>Note</b>: That change should be called inside a rename operation as it assumes that a {@link RenameResourceChange} occured. * * @author tszadel * */ public class RenameModelChange extends Change { private final Map<URI, URI> uriMap = new HashMap<URI, URI>(); private final IFile oldFile; private final IFile newFile; private final Set<IResource> relatedFiles; private final Collection<? extends IResource> impacted; private TransactionalEditingDomain domain; private ModelSet resourceSet; private List<IMultiDiagramEditor> openedEditors; private boolean isUndoOperation; /** * Constructor. * * @param resourceSet * The resource set being changed. * @param oldFile * The old file. * @param newFile * The new file. * @param impacted */ public RenameModelChange(IFile oldFile, IFile newFile, Collection<? extends IResource> impacted) { this.oldFile = oldFile; this.newFile = newFile; this.impacted = impacted; IPath newPathWithoutExt = newFile.getFullPath().removeFileExtension(); // Create the map of URI that are being modified in the resource set relatedFiles = ModelParticipantHelpers.getRelatedFiles(oldFile); relatedFiles.add(oldFile); for(IResource file : relatedFiles) { IPath path = file.getFullPath(); URI oldURI = getPlatformURI(path); URI newURI = getPlatformURI(newPathWithoutExt.addFileExtension(path.getFileExtension())); uriMap.put(oldURI, newURI); } } /** * Overrides getModifiedElement. * * {@inheritDoc} * * @see org.eclipse.ltk.core.refactoring.Change#getModifiedElement() */ @Override public Object getModifiedElement() { return oldFile; } /** * Overrides getName. * * {@inheritDoc} * * @see org.eclipse.ltk.core.refactoring.Change#getName() */ @Override public String getName() { return Messages.bind(Messages.RenameModelChange_Name, oldFile.getName()); } /** * Overrides initializeValidationData. * * {@inheritDoc} * * @see org.eclipse.ltk.core.refactoring.Change#initializeValidationData(org.eclipse.core.runtime.IProgressMonitor) */ @Override public void initializeValidationData(IProgressMonitor pm) { // Nothing } /** * Overrides isValid. * * {@inheritDoc} * * @see org.eclipse.ltk.core.refactoring.Change#isValid(org.eclipse.core.runtime.IProgressMonitor) */ @Override public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException { // That change assumes that the model resource has already been renamed // So, the first thing to do is to get the new file, to restore it in order // to change all the resources. pm.subTask(Messages.RenameModelChange_LoadingEMF); // String extension= newFile.getFileExtension(); // if( extension == null){ // newFile.getFullPath().addFileExtension(oldFile.getFileExtension()); // } pm.subTask(Messages.RenameModelChange_DaveDirtyEditor); // We need to get the current workbench... so we have to use the UI-Thread! openedEditors = new ArrayList<IMultiDiagramEditor>(); Display.getDefault().syncExec(new Runnable() { public void run() { IMultiDiagramEditor[] multiEditors = EditorUtils.getRelatedEditors(oldFile); if(multiEditors != null && multiEditors.length > 0) { for(IMultiDiagramEditor editor : multiEditors) { if(editor.isDirty()) { editor.doSave(new NullProgressMonitor()); } openedEditors.add(editor); } } } }); pm.worked(10); /* * Load ModelSet */ resourceSet = new ModelSet(); // XMIResourceFactoryImpl value = new XMIResourceFactoryImpl(); // resourceSet.getResourceFactoryRegistry().getContentTypeToFactoryMap().put(Resource.Factory.Registry.DEFAULT_CONTENT_TYPE_IDENTIFIER, value); // resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, value); try { ModelsReader reader = new ModelsReader(); reader.readModel(resourceSet); resourceSet.loadModels(oldFile); for(IResource r : impacted) { if(r instanceof IFile) { IFile file = (IFile)r; try { resourceSet.getResource(URI.createPlatformResourceURI(file.getFullPath().toString(), true), true); } catch (Exception e) { // to avoid load errors } } } } catch (Exception e) { e.printStackTrace(); } // Force EMF resolve and load all the resources try { EcoreUtil.resolveAll(resourceSet); } catch (RuntimeException e) { // the resolve all does not have to break the operation } pm.worked(4); domain = resourceSet.getTransactionalEditingDomain(); // TODO improve when impact analysis will be effective return manageResourceSet(pm, resourceSet); } /** * Get a platform resource URI of the given path * * @param path * the path * @return the uri */ private URI getPlatformURI(IPath path) { return URI.createPlatformResourceURI(path.toString(), true); } /** * Overrides perform. * * {@inheritDoc} * * @see org.eclipse.ltk.core.refactoring.Change#perform(org.eclipse.core.runtime.IProgressMonitor) */ @Override public Change perform(IProgressMonitor pm) throws CoreException { String lMsg = Messages.bind(Messages.RenameModelChange_Change, oldFile.getName(), newFile.getName()); isUndoOperation = oldFile.exists() && !newFile.exists(); if(!isUndoOperation) { newFile.move(oldFile.getFullPath(), true, new SubProgressMonitor(pm, 1)); } pm.beginTask(lMsg, 30); try { doRun(pm, resourceSet, domain); // Now, save all the resources pm.subTask(Messages.RenameModelChange_savingResource); for(Resource res : resourceSet.getResources()) { if(res.getURI().isPlatformResource()) { try { res.save(null); } catch (Exception e) { log.error(Messages.bind(Messages.RenameModelChange_ErrorLoading, res.getURI()), e); } } } pm.worked(5); // Do not forget to unload all the resources to avoid memory leak pm.subTask(Messages.RenameModelChange_Unloading); resourceSet.unload(); pm.worked(1); // Now, notify the editor of the change if(!openedEditors.isEmpty()) { Display.getDefault().syncExec(new Runnable() { public void run() { // Get the DI file as the rename could occur on any model's file. IFile newDiFile = DiModelUtils.getRelatedDiFile(newFile); for(IMultiDiagramEditor editor : openedEditors) { try { DiResourceSet diRes = editor.getServicesRegistry().getService(DiResourceSet.class); if(diRes != null) { diRes.saveAs(newFile.getFullPath()); } editor.setEditorInput(new FileEditorInput(newDiFile)); } catch (ServiceException e) { log.error(e); } catch (IOException e) { log.error(e); } } } }); } // Then, remove the old model files pm.subTask(Messages.RenameModelChange_RemoveOldFile); for(IResource file : relatedFiles) { if(file.exists()) { file.delete(true, new NullProgressMonitor()); } } pm.worked(4); if(isUndoOperation) { newFile.move(oldFile.getFullPath(), true, new SubProgressMonitor(pm, 1)); } return new RenameModelChange(newFile, oldFile, impacted); } finally { pm.done(); } } private RefactoringStatus manageResourceSet(final IProgressMonitor pm, final ModelSet resourceSet) { final Collection<Resource> readOnlies = new HashSet<Resource>(); // for each object of the resources renamed, the refactor will search if a resource is read only or not domain.getCommandStack().execute(new RecordingCommand(domain) { @Override protected void doExecute() { for(URI uri : uriMap.keySet()) { Resource r = resourceSet.getResource(uri, false); ECrossReferenceAdapter adapter = ECrossReferenceAdapter.getCrossReferenceAdapter(resourceSet); /* apex improved start */ // 문맥상 맞지 않기도 하고, adapter==null인 경우 에러발생 if(adapter == null) { /* apex improved end */ /* apex replaced if(adapter != null) { */ adapter = new ECrossReferenceAdapter(); adapter.setTarget(resourceSet); } if(r != null) { for(Iterator<EObject> i = EcoreUtil.getAllProperContents(r, false); i.hasNext();) { EObject e = i.next(); Collection<Setting> references = adapter.getInverseReferences(e); for(Setting s : references) { EObject eObject = s.getEObject(); if(eObject != null && eObject.eResource() != null && domain.isReadOnly(eObject.eResource())) { readOnlies.add(eObject.eResource()); } } } } } } }); // if read only => error to the user if(!readOnlies.isEmpty()) { StringBuffer buffer = new StringBuffer("The resources listed are read only, the rename process can not continue : "); int i = 0; for(Resource r : readOnlies) { if(i != 0) { buffer.append(", "); } buffer.append(r.getURI().toString()); i++; } return RefactoringStatus.create(new Status(IStatus.ERROR, Activator.PLUGIN_ID, buffer.toString())); } return new RefactoringStatus(); } private void doRun(final IProgressMonitor pm, final ModelSet resourceSet, final TransactionalEditingDomain domain) { domain.getCommandStack().execute(new RecordingCommand(domain) { @Override protected void doExecute() { // Manage Controlled map to ensure consistent history // TODO change this code when history will be useless URI modifiedURI = URI.createPlatformResourceURI(oldFile.getFullPath().removeFileExtension().toString(), true); IModelSetQueryAdapter controledResourcesAdapter = ModelSetQuery.getExistingTypeCacheAdapter(resourceSet); if(controledResourcesAdapter != null) { EObject first = null; for(Iterator<Notifier> i = resourceSet.getAllContents(); i.hasNext();) { Notifier n = i.next(); if(n instanceof EObject) { first = (EObject)n; break; } } if(first != null) { Collection<EObject> resources = null; try { resources = controledResourcesAdapter.getReachableObjectsOfType(first, historyPackage.Literals.CONTROLED_RESOURCE); } catch (RuntimeException e) { // in case of errors integrity must be valid // even performances are bad resources = new LinkedList<EObject>(); for(int i = 0; i < resourceSet.getResources().size(); i++) { Resource r = resourceSet.getResources().get(i); for(Iterator<EObject> it = r.getAllContents(); it.hasNext();) { EObject tmp = it.next(); if(tmp instanceof ControledResource) { ControledResource controled = (ControledResource)tmp; resources.add(controled); } } } } for(EObject e : resources) { if(e instanceof ControledResource) { ControledResource controled = (ControledResource)e; URI baseURI = URI.createURI(e.eResource().getURI().trimSegments(1).trimFragment().toString() + "/"); URI resolvedURI = URI.createURI(controled.getResourceURL()).resolve(baseURI); if(resolvedURI.trimFileExtension().equals(modifiedURI.trimFileExtension())) { String ext = resolvedURI.fileExtension(); URI newURL = URI.createURI(resolvedURI.trimSegments(1).toString() + "/" + newFile.getFullPath().removeFileExtension().lastSegment().toString() + "." + ext); controled.setResourceURL(newURL.deresolve(baseURI).toString()); } } } } } } }); // Change the uri of the files pm.subTask(Messages.RenameModelChange_ModifyURI); for(Resource res : resourceSet.getResources()) { if(res.getURI().isPlatformResource()) { URI newURI = uriMap.get(res.getURI()); if(newURI != null) { if(log.isDebugEnabled()) { log.debug(Messages.bind(Messages.RenameModelChange_6, Arrays.asList(res.getURI(), newURI))); } res.setURI(newURI); } } } pm.worked(5); } }