| /******************************************************************************* |
| * Copyright (c) 2005, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.ltk.internal.core.refactoring.history; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| import com.ibm.icu.text.DateFormat; |
| |
| import org.xml.sax.InputSource; |
| |
| import org.eclipse.core.commands.operations.IOperationHistoryListener; |
| import org.eclipse.core.commands.operations.IUndoableOperation; |
| import org.eclipse.core.commands.operations.OperationHistoryEvent; |
| import org.eclipse.core.commands.operations.OperationHistoryFactory; |
| import org.eclipse.core.commands.operations.TriggeredOperations; |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileStore; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.MultiStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.core.runtime.preferences.IScopeContext; |
| |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceChangeEvent; |
| import org.eclipse.core.resources.IResourceChangeListener; |
| import org.eclipse.core.resources.IResourceDelta; |
| import org.eclipse.core.resources.ProjectScope; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| |
| import org.eclipse.ltk.core.refactoring.ChangeDescriptor; |
| import org.eclipse.ltk.core.refactoring.IRefactoringCoreStatusCodes; |
| import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; |
| import org.eclipse.ltk.core.refactoring.RefactoringDescriptorProxy; |
| import org.eclipse.ltk.core.refactoring.RefactoringSessionDescriptor; |
| import org.eclipse.ltk.core.refactoring.history.IRefactoringExecutionListener; |
| import org.eclipse.ltk.core.refactoring.history.IRefactoringHistoryListener; |
| import org.eclipse.ltk.core.refactoring.history.IRefactoringHistoryService; |
| import org.eclipse.ltk.core.refactoring.history.RefactoringExecutionEvent; |
| import org.eclipse.ltk.core.refactoring.history.RefactoringHistory; |
| import org.eclipse.ltk.core.refactoring.history.RefactoringHistoryEvent; |
| import org.eclipse.ltk.internal.core.refactoring.IRefactoringSerializationConstants; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringCorePlugin; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringPreferenceConstants; |
| import org.eclipse.ltk.internal.core.refactoring.RefactoringSessionReader; |
| import org.eclipse.ltk.internal.core.refactoring.UndoableOperation2ChangeAdapter; |
| |
| /** |
| * Default implementation of a refactoring history service. |
| * |
| * @since 3.2 |
| */ |
| public final class RefactoringHistoryService implements IRefactoringHistoryService { |
| |
| /** The null refactoring history */ |
| private static final class NullRefactoringHistory extends RefactoringHistory { |
| |
| /** The no proxies constant */ |
| private static final RefactoringDescriptorProxy[] NO_PROXIES= {}; |
| |
| @Override |
| public RefactoringDescriptorProxy[] getDescriptors() { |
| return NO_PROXIES; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| |
| @Override |
| public RefactoringHistory removeAll(final RefactoringHistory history) { |
| return this; |
| } |
| } |
| |
| /** The singleton history */ |
| private static RefactoringHistoryService fInstance= null; |
| |
| /** The refactoring history file */ |
| public static final String NAME_HISTORY_FILE= "refactorings.history"; //$NON-NLS-1$ |
| |
| /** The refactoring history folder */ |
| public static final String NAME_HISTORY_FOLDER= ".refactorings"; //$NON-NLS-1$ |
| |
| /** The refactoring history index file name */ |
| public static final String NAME_INDEX_FILE= "refactorings.index"; //$NON-NLS-1$ |
| |
| /** The name of the special workspace project */ |
| public static final String NAME_WORKSPACE_PROJECT= ".workspace"; //$NON-NLS-1$ |
| |
| /** The no history constant */ |
| private static final NullRefactoringHistory NO_HISTORY= new NullRefactoringHistory(); |
| |
| /** |
| * Filters the given array of refactoring proxies and returns the result in |
| * the specified refactoring descriptor proxy set. |
| * <p> |
| * Clients wishing to benefit from the resolving of refactoring descriptors |
| * to determine its flags can set resolve to <code>true</code> if they |
| * would like to have resolved refactoring descriptor proxies as result. |
| * </p> |
| * |
| * @param proxies |
| * the refactoring descriptor proxies |
| * @param set |
| * the result set |
| * @param resolve |
| * <code>true</code> to return the filtered refactoring |
| * descriptors as resolved refactoring proxies, |
| * <code>false</code> otherwise |
| * @param flags |
| * the refactoring descriptor flags which must be present in |
| * order to be returned in the refactoring history object |
| * @param monitor |
| * the progress monitor to use |
| */ |
| private static void filterRefactoringDescriptors(final RefactoringDescriptorProxy[] proxies, final Set<RefactoringDescriptorProxy> set, final boolean resolve, final int flags, final IProgressMonitor monitor) { |
| Assert.isTrue(flags > RefactoringDescriptor.NONE); |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_retrieving_history, proxies.length); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| final RefactoringDescriptor descriptor= proxy.requestDescriptor(new SubProgressMonitor(monitor, 1)); |
| if (descriptor != null) { |
| final int filter= descriptor.getFlags(); |
| if ((filter | flags) == filter) { |
| if (resolve) { |
| set.add(new RefactoringDescriptorProxyAdapter(descriptor)); |
| } else { |
| set.add(proxy); |
| } |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Returns the singleton instance of the refactoring history. |
| * |
| * @return the singleton instance |
| */ |
| public static RefactoringHistoryService getInstance() { |
| if (fInstance == null) |
| fInstance= new RefactoringHistoryService(); |
| return fInstance; |
| } |
| |
| /** |
| * Returns whether a project has a shared refactoring history. |
| * |
| * @param project |
| * the project to test |
| * @return <code>true</code> if the project has a shared project history, |
| * <code>false</code> otherwise |
| */ |
| public static boolean hasSharedRefactoringHistory(final IProject project) { |
| Assert.isNotNull(project); |
| final IScopeContext[] contexts= new IScopeContext[] { new ProjectScope(project)}; |
| final String preference= Platform.getPreferencesService().getString(RefactoringCorePlugin.getPluginId(), RefactoringPreferenceConstants.PREFERENCE_SHARED_REFACTORING_HISTORY, Boolean.FALSE.toString(), contexts); |
| if (preference != null) |
| return Boolean.valueOf(preference).booleanValue(); |
| return false; |
| } |
| |
| /** |
| * Determines whether a project has a shared refactoring history. |
| * <p> |
| * If a shared refactoring history is enabled, refactorings executed on that |
| * particular project are stored in a hidden refactoring history folder of |
| * the project folder. If no shared refactoring history is enabled, all |
| * refactorings are tracked as well, but persisted internally in a |
| * plugin-specific way without altering the project. |
| * </p> |
| * <p> |
| * Note: this method simply copies the content of the refactoring history |
| * folder to the location corresponding to the shared history setting. |
| * Clients wishing to programmatically change the refactoring history |
| * location have to update the preference |
| * {@link RefactoringPreferenceConstants#PREFERENCE_SHARED_REFACTORING_HISTORY} |
| * located in the preference store of the |
| * <code>org.eclipse.ltk.core.refactoring</code> plugin accordingly. |
| * </p> |
| * |
| * @param project |
| * the project to set the shared refactoring history property |
| * @param enable |
| * <code>true</code> to enable a shared refactoring history, |
| * <code>false</code> otherwise |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * @throws CoreException |
| * if an error occurs while changing the shared refactoring |
| * history property. Reasons include: |
| * <ul> |
| * <li>An I/O error occurs while changing the shared |
| * refactoring history property.</li> |
| * </ul> |
| */ |
| public static void setSharedRefactoringHistory(final IProject project, final boolean enable, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(project); |
| Assert.isTrue(project.isAccessible()); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask("", 300); //$NON-NLS-1$ |
| final String name= project.getName(); |
| final URI uri= project.getLocationURI(); |
| if (uri != null) { |
| try { |
| final IFileStore history= EFS.getLocalFileSystem().getStore(RefactoringCorePlugin.getDefault().getStateLocation()).getChild(NAME_HISTORY_FOLDER); |
| if (enable) { |
| final IFileStore source= history.getChild(name); |
| if (source.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20)).exists()) { |
| IFileStore destination= EFS.getStore(uri).getChild(NAME_HISTORY_FOLDER); |
| if (destination.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20)).exists()) |
| destination.delete(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| destination.mkdir(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| source.copy(destination, EFS.OVERWRITE, new SubProgressMonitor(monitor, 20)); |
| source.delete(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| } |
| } else { |
| final IFileStore source= EFS.getStore(uri).getChild(NAME_HISTORY_FOLDER); |
| if (source.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20)).exists()) { |
| IFileStore destination= history.getChild(name); |
| if (destination.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 20)).exists()) |
| destination.delete(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| destination.mkdir(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| source.copy(destination, EFS.OVERWRITE, new SubProgressMonitor(monitor, 20)); |
| source.delete(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| } |
| } |
| } finally { |
| if (enable) |
| project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 30)); |
| else { |
| final IFolder folder= project.getFolder(NAME_HISTORY_FOLDER); |
| if (folder.exists()) |
| folder.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 30)); |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** The execution listeners */ |
| private final ListenerList<IRefactoringExecutionListener> fExecutionListeners= new ListenerList<>(ListenerList.EQUALITY); |
| |
| /** The history listeners */ |
| private final ListenerList<IRefactoringHistoryListener> fHistoryListeners= new ListenerList<>(ListenerList.EQUALITY); |
| |
| /** The operation listener, or <code>null</code> */ |
| private IOperationHistoryListener fOperationListener= null; |
| |
| /** The override time stamp */ |
| private long fOverrideTimeStamp= -1; |
| |
| /** The history reference count */ |
| private int fReferenceCount= 0; |
| |
| /** The resource listener, or <code>null</code> */ |
| private IResourceChangeListener fResourceListener= null; |
| |
| /** Maximal number of refactoring managers */ |
| private static final int MAX_MANAGERS= 2; |
| |
| /** The refactoring history manager cache */ |
| private final Map<IFileStore, RefactoringHistoryManager> fManagerCache= new LinkedHashMap<IFileStore, RefactoringHistoryManager>(MAX_MANAGERS, 0.75f, true) { |
| |
| private static final long serialVersionUID= 1L; |
| |
| @Override |
| protected boolean removeEldestEntry(Map.Entry<IFileStore, RefactoringHistoryManager> entry) { |
| return size() > MAX_MANAGERS; |
| } |
| }; |
| |
| /** |
| * Creates a new refactoring history. |
| */ |
| private RefactoringHistoryService() { |
| // Do nothing |
| } |
| |
| @Override |
| public void addExecutionListener(final IRefactoringExecutionListener listener) { |
| Assert.isNotNull(listener); |
| fExecutionListeners.add(listener); |
| } |
| |
| @Override |
| public void addHistoryListener(final IRefactoringHistoryListener listener) { |
| Assert.isNotNull(listener); |
| fHistoryListeners.add(listener); |
| } |
| |
| /** |
| * Adds the specified refactoring descriptor to the corresponding |
| * refactoring history. |
| * <p> |
| * If a descriptor with the same timestamp already exists, nothing happens. |
| * </p> |
| * |
| * @param proxy |
| * the refactoring descriptor proxy |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| */ |
| public void addRefactoringDescriptor(final RefactoringDescriptorProxy proxy, IProgressMonitor monitor) { |
| Assert.isNotNull(proxy); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| fireRefactoringHistoryEvent(proxy, RefactoringHistoryEvent.ADDED); |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void connect() { |
| fReferenceCount++; |
| if (fReferenceCount == 1) { |
| fOperationListener= new IOperationHistoryListener() { |
| @Override |
| public void historyNotification(final OperationHistoryEvent event) { |
| performHistoryNotification(event); |
| } |
| }; |
| OperationHistoryFactory.getOperationHistory().addOperationHistoryListener(fOperationListener); |
| |
| fResourceListener= new IResourceChangeListener() { |
| @Override |
| public void resourceChanged(final IResourceChangeEvent event) { |
| peformResourceChanged(event); |
| } |
| }; |
| ResourcesPlugin.getWorkspace().addResourceChangeListener(fResourceListener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.POST_CHANGE); |
| } |
| } |
| |
| /** |
| * Deletes the specified refactoring descriptors from their associated |
| * refactoring histories. |
| * |
| * @param proxies |
| * the refactoring descriptor proxies |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * @throws CoreException |
| * if an error occurs while deleting the refactoring |
| * descriptors. Reasons include: |
| * <ul> |
| * <li>The refactoring history has an illegal format, contains |
| * illegal arguments or otherwise illegal information.</li> |
| * <li>An I/O error occurs while deleting the refactoring |
| * descriptors from the refactoring history.</li> |
| * </ul> |
| * |
| * @see IRefactoringCoreStatusCodes#REFACTORING_HISTORY_FORMAT_ERROR |
| * @see IRefactoringCoreStatusCodes#REFACTORING_HISTORY_IO_ERROR |
| */ |
| public void deleteRefactoringDescriptors(final RefactoringDescriptorProxy[] proxies, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(proxies); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_deleting_refactorings, proxies.length + 300); |
| final Map<String, Collection<RefactoringDescriptorProxy>> projects= new HashMap<>(); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| String project= proxy.getProject(); |
| if (project == null || "".equals(project)) //$NON-NLS-1$ |
| project= RefactoringHistoryService.NAME_WORKSPACE_PROJECT; |
| Collection<RefactoringDescriptorProxy> collection= projects.get(project); |
| if (collection == null) { |
| collection= new ArrayList<>(); |
| projects.put(project, collection); |
| } |
| collection.add(proxy); |
| monitor.worked(1); |
| } |
| final SubProgressMonitor subMonitor= new SubProgressMonitor(monitor, 300); |
| try { |
| final Set<Entry<String, Collection<RefactoringDescriptorProxy>>> entries= projects.entrySet(); |
| subMonitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_deleting_refactorings, entries.size()); |
| for (final Iterator<Entry<String, Collection<RefactoringDescriptorProxy>>> iterator= entries.iterator(); iterator.hasNext();) { |
| final Entry<String, Collection<RefactoringDescriptorProxy>> entry= iterator.next(); |
| final Collection<RefactoringDescriptorProxy> collection= entry.getValue(); |
| String project= entry.getKey(); |
| if (project.equals(RefactoringHistoryService.NAME_WORKSPACE_PROJECT)) |
| project= null; |
| final RefactoringHistoryManager manager= getManager(project); |
| if (manager != null) |
| manager.removeRefactoringDescriptors(collection.toArray(new RefactoringDescriptorProxy[collection.size()]), new SubProgressMonitor(subMonitor, 1), RefactoringCoreMessages.RefactoringHistoryService_deleting_refactorings); |
| else |
| subMonitor.worked(1); |
| } |
| } finally { |
| subMonitor.done(); |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Deletes the specified refactoring descriptors from their associated |
| * refactoring histories. |
| * |
| * @param proxies |
| * the refactoring descriptor proxies |
| * @param query |
| * the refactoring descriptor delete query to use |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * @throws CoreException |
| * if an error occurs while deleting the refactoring |
| * descriptors. Reasons include: |
| * <ul> |
| * <li>The refactoring history has an illegal format, contains |
| * illegal arguments or otherwise illegal information.</li> |
| * <li>An I/O error occurs while deleting the refactoring |
| * descriptors from the refactoring history.</li> |
| * </ul> |
| * |
| * @see IRefactoringCoreStatusCodes#REFACTORING_HISTORY_FORMAT_ERROR |
| * @see IRefactoringCoreStatusCodes#REFACTORING_HISTORY_IO_ERROR |
| */ |
| public void deleteRefactoringDescriptors(final RefactoringDescriptorProxy[] proxies, final IRefactoringDescriptorDeleteQuery query, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(proxies); |
| Assert.isNotNull(query); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_deleting_refactorings, proxies.length + 300); |
| final Set<RefactoringDescriptorProxy> set= new HashSet<>(proxies.length); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| if (query.proceed(proxy).isOK()) { |
| set.add(proxy); |
| } |
| monitor.worked(1); |
| } |
| if (!set.isEmpty()) { |
| final RefactoringDescriptorProxy[] delete= set.toArray(new RefactoringDescriptorProxy[set.size()]); |
| deleteRefactoringDescriptors(delete, new SubProgressMonitor(monitor, 300)); |
| for (RefactoringDescriptorProxy d : delete) { |
| fireRefactoringHistoryEvent(d, RefactoringHistoryEvent.DELETED); |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| /** |
| * Deletes the refactoring history of a project. Refactorings associated |
| * with the workspace are not deleted. |
| * <p> |
| * If a refactoring history is deleted, all files stored in the hidden |
| * refactoring history folder of the project folder are removed. If no |
| * shared refactoring history is enabled, the refactoring history |
| * information is removed from the internal workspace refactoring history. |
| * </p> |
| * |
| * @param project |
| * the project to delete its history |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * @throws CoreException |
| * if an error occurs while deleting the refactoring history. |
| * Reasons include: |
| * <ul> |
| * <li>An I/O error occurs while deleting the refactoring |
| * history.</li> |
| * </ul> |
| */ |
| public void deleteRefactoringHistory(final IProject project, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(project); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_deleting_refactorings, 100); |
| final String name= project.getName(); |
| final IFileStore stateStore= EFS.getLocalFileSystem().getStore(RefactoringCorePlugin.getDefault().getStateLocation()); |
| if (name.equals(NAME_WORKSPACE_PROJECT)) { |
| final IFileStore metaStore= stateStore.getChild(NAME_HISTORY_FOLDER).getChild(name); |
| metaStore.delete(EFS.NONE, new SubProgressMonitor(monitor, 100)); |
| } else { |
| final URI uri= project.getLocationURI(); |
| if (uri != null && project.isAccessible()) { |
| try { |
| final IFileStore metaStore= stateStore.getChild(NAME_HISTORY_FOLDER).getChild(name); |
| metaStore.delete(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| final IFileStore projectStore= EFS.getStore(uri).getChild(NAME_HISTORY_FOLDER); |
| projectStore.delete(EFS.NONE, new SubProgressMonitor(monitor, 20)); |
| } finally { |
| project.refreshLocal(IResource.DEPTH_INFINITE, new SubProgressMonitor(monitor, 60)); |
| } |
| } |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public void disconnect() { |
| if (fReferenceCount > 0) { |
| fManagerCache.clear(); |
| fReferenceCount--; |
| } |
| if (fReferenceCount == 0) { |
| if (fOperationListener != null) |
| OperationHistoryFactory.getOperationHistory().removeOperationHistoryListener(fOperationListener); |
| if (fResourceListener != null) |
| ResourcesPlugin.getWorkspace().removeResourceChangeListener(fResourceListener); |
| fOperationListener= null; |
| } |
| } |
| |
| private void fireRefactoringExecutionEvent(final RefactoringDescriptorProxy proxy, final int eventType) { |
| Assert.isNotNull(proxy); |
| for (final IRefactoringExecutionListener listener : fExecutionListeners) { |
| SafeRunner.run(new ISafeRunnable() { |
| |
| @Override |
| public void handleException(final Throwable throwable) { |
| RefactoringCorePlugin.log(throwable); |
| } |
| |
| @Override |
| public void run() throws Exception { |
| listener.executionNotification(new RefactoringExecutionEvent(RefactoringHistoryService.this, eventType, proxy)); |
| } |
| }); |
| } |
| } |
| |
| private void fireRefactoringHistoryEvent(final RefactoringDescriptorProxy proxy, final int eventType) { |
| Assert.isNotNull(proxy); |
| for (final IRefactoringHistoryListener listener : fHistoryListeners) { |
| SafeRunner.run(new ISafeRunnable() { |
| |
| @Override |
| public void handleException(final Throwable throwable) { |
| RefactoringCorePlugin.log(throwable); |
| } |
| |
| @Override |
| public void run() throws Exception { |
| listener.historyNotification(new RefactoringHistoryEvent(RefactoringHistoryService.this, eventType, proxy)); |
| } |
| }); |
| } |
| } |
| |
| private boolean checkDescriptor(RefactoringDescriptor descriptor, IUndoableOperation operation) { |
| Assert.isNotNull(descriptor); |
| try { |
| final Map<String, String> arguments= RefactoringHistoryManager.getArgumentMap(descriptor); |
| if (arguments != null) |
| RefactoringHistoryManager.checkArgumentMap(arguments); |
| } catch (CoreException exception) { |
| final IStatus status= exception.getStatus(); |
| if (status.getCode() == IRefactoringCoreStatusCodes.REFACTORING_HISTORY_FORMAT_ERROR) { |
| final String time= DateFormat.getDateTimeInstance().format(new Date(descriptor.getTimeStamp())); |
| final String message= "The refactoring executed at " + time + " contributed a refactoring descriptor with invalid format:"; //$NON-NLS-1$//$NON-NLS-2$ |
| final IStatus comment= new Status(IStatus.ERROR, RefactoringCorePlugin.getPluginId(), descriptor.getComment()); |
| RefactoringCorePlugin.log(new MultiStatus(RefactoringCorePlugin.getPluginId(), 0, new IStatus[] { comment}, message, null)); |
| } |
| RefactoringCorePlugin.log(exception); |
| |
| if (operation instanceof TriggeredOperations) { |
| operation= ((TriggeredOperations) operation).getTriggeringOperation(); |
| } |
| if (operation instanceof UndoableOperation2ChangeAdapter) { |
| ((UndoableOperation2ChangeAdapter) operation).setChangeDescriptor(null); |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public RefactoringHistory getProjectHistory(final IProject project, IProgressMonitor monitor) { |
| return getProjectHistory(project, 0, Long.MAX_VALUE, RefactoringDescriptor.NONE, monitor); |
| } |
| |
| @Override |
| public RefactoringHistory getProjectHistory(final IProject project, final long start, final long end, final int flags, IProgressMonitor monitor) { |
| Assert.isNotNull(project); |
| Assert.isTrue(project.exists()); |
| Assert.isTrue(start >= 0); |
| Assert.isTrue(end >= 0); |
| Assert.isTrue(flags >= RefactoringDescriptor.NONE); |
| if (project.isOpen()) { |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_retrieving_history, 120); |
| final String name= project.getName(); |
| final RefactoringHistoryManager manager= getManager(name); |
| if (manager != null) { |
| RefactoringHistory history= manager.readRefactoringHistory(start, end, new SubProgressMonitor(monitor, 20)); |
| if (flags > RefactoringDescriptor.NONE) { |
| final Set<RefactoringDescriptorProxy> set= new HashSet<>(); |
| filterRefactoringDescriptors(history.getDescriptors(), set, false, flags, new SubProgressMonitor(monitor, 100)); |
| history= new RefactoringHistoryImplementation(set.toArray(new RefactoringDescriptorProxy[set.size()])); |
| } |
| return history; |
| } |
| } finally { |
| monitor.done(); |
| } |
| } |
| return NO_HISTORY; |
| } |
| |
| @Override |
| public RefactoringHistory getRefactoringHistory(final IProject[] projects, final IProgressMonitor monitor) { |
| return getRefactoringHistory(projects, 0, Long.MAX_VALUE, RefactoringDescriptor.NONE, monitor); |
| } |
| |
| @Override |
| public RefactoringHistory getRefactoringHistory(final IProject[] projects, final long start, final long end, final int flags, IProgressMonitor monitor) { |
| Assert.isNotNull(projects); |
| Assert.isTrue(start >= 0); |
| Assert.isTrue(end >= start); |
| Assert.isTrue(flags >= RefactoringDescriptor.NONE); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_retrieving_history, 3 * projects.length); |
| final Set<RefactoringDescriptorProxy> set= new HashSet<>(); |
| if (flags > RefactoringDescriptor.NONE) { |
| for (IProject project : projects) { |
| if (project.isAccessible()) { |
| final RefactoringDescriptorProxy[] proxies= getProjectHistory(project, start, end, flags, new SubProgressMonitor(monitor, 1)).getDescriptors(); |
| filterRefactoringDescriptors(proxies, set, false, flags, new SubProgressMonitor(monitor, 2)); |
| } |
| } |
| } else { |
| for (IProject project : projects) { |
| if (project.isAccessible()) { |
| final RefactoringDescriptorProxy[] proxies= getProjectHistory(project, start, end, RefactoringDescriptor.NONE, new SubProgressMonitor(monitor, 3)).getDescriptors(); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| set.add(proxy); |
| } |
| } |
| } |
| } |
| final RefactoringDescriptorProxy[] proxies= new RefactoringDescriptorProxy[set.size()]; |
| set.toArray(proxies); |
| return new RefactoringHistoryImplementation(proxies); |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| @Override |
| public RefactoringHistory getWorkspaceHistory(IProgressMonitor monitor) { |
| return getWorkspaceHistory(0, Long.MAX_VALUE, monitor); |
| } |
| |
| @Override |
| public RefactoringHistory getWorkspaceHistory(final long start, final long end, IProgressMonitor monitor) { |
| return getRefactoringHistory(ResourcesPlugin.getWorkspace().getRoot().getProjects(), start, end, RefactoringDescriptor.NONE, monitor); |
| } |
| |
| /** |
| * Reads refactoring descriptor proxies from the input stream. |
| * <p> |
| * Note that calling this method with a flag argument unequal to |
| * <code>RefactoringDescriptor#NONE</code> may result in a performance |
| * degradation, since the actual descriptors have to be eagerly resolved. |
| * This in turn results in faster execution of any subsequent calls to |
| * {@link RefactoringDescriptorProxy#requestDescriptor(IProgressMonitor)} |
| * which try to request a descriptor from the returned refactoring history. |
| * </p> |
| * |
| * @param stream |
| * the input stream to read from |
| * @return the refactoring descriptor proxies |
| * @throws CoreException |
| * if an error occurs while reading the refactoring descriptor |
| * proxies |
| */ |
| public RefactoringDescriptorProxy[] readRefactoringDescriptorProxies(final InputStream stream) throws CoreException { |
| Assert.isNotNull(stream); |
| try { |
| return RefactoringHistoryManager.readRefactoringDescriptorProxies(stream, null, 0, Long.MAX_VALUE); |
| } catch (IOException exception) { |
| throw new CoreException(new Status(IStatus.ERROR, RefactoringCorePlugin.getPluginId(), 0, exception.getLocalizedMessage(), null)); |
| } |
| } |
| |
| @Override |
| public RefactoringHistory readRefactoringHistory(final InputStream stream, final int flags) throws CoreException { |
| Assert.isNotNull(stream); |
| Assert.isTrue(flags >= RefactoringDescriptor.NONE); |
| final List<RefactoringDescriptor> list= new ArrayList<>(); |
| final RefactoringSessionDescriptor descriptor= new RefactoringSessionReader(false, null).readSession(new InputSource(stream)); |
| if (descriptor != null) { |
| final RefactoringDescriptor[] descriptors= descriptor.getRefactorings(); |
| if (flags > RefactoringDescriptor.NONE) { |
| for (RefactoringDescriptor d : descriptors) { |
| final int current= d.getFlags(); |
| if ((current | flags) == current) { |
| list.add(d); |
| } |
| } |
| } else |
| list.addAll(Arrays.asList(descriptors)); |
| } |
| final RefactoringDescriptorProxy[] proxies= new RefactoringDescriptorProxy[list.size()]; |
| for (int index= 0; index < list.size(); index++) |
| proxies[index]= new RefactoringDescriptorProxyAdapter(list.get(index)); |
| return new RefactoringHistoryImplementation(proxies); |
| } |
| |
| @Override |
| public void removeExecutionListener(final IRefactoringExecutionListener listener) { |
| Assert.isNotNull(listener); |
| fExecutionListeners.remove(listener); |
| } |
| |
| @Override |
| public void removeHistoryListener(final IRefactoringHistoryListener listener) { |
| Assert.isNotNull(listener); |
| fHistoryListeners.remove(listener); |
| } |
| |
| /** |
| * Returns the resolved refactoring descriptor associated with the specified |
| * proxy. |
| * <p> |
| * The refactoring history must be in connected state. |
| * </p> |
| * |
| * @param proxy |
| * the refactoring descriptor proxy |
| * @param monitor |
| * the progress monitor to use, or <code>null</code> |
| * |
| * @return the resolved refactoring descriptor, or <code>null</code> |
| */ |
| public RefactoringDescriptor requestDescriptor(final RefactoringDescriptorProxy proxy, IProgressMonitor monitor) { |
| Assert.isNotNull(proxy); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| final RefactoringHistoryManager manager= getManager(proxy.getProject()); |
| if (manager != null) |
| return manager.requestDescriptor(proxy, monitor); |
| } finally { |
| monitor.done(); |
| } |
| return null; |
| } |
| |
| /** |
| * Sets the override time stamp for the next refactoring performed. |
| * |
| * @param stamp |
| * the override time stamp, or <code>-1</code> to clear it |
| */ |
| public void setOverrideTimeStamp(final long stamp) { |
| Assert.isTrue(stamp == -1 || stamp >= 0); |
| fOverrideTimeStamp= stamp; |
| } |
| |
| @Override |
| public void writeRefactoringDescriptors(final RefactoringDescriptorProxy[] proxies, final OutputStream stream, final int flags, final boolean time, IProgressMonitor monitor) throws CoreException { |
| Assert.isNotNull(proxies); |
| Assert.isNotNull(stream); |
| Assert.isTrue(flags >= RefactoringDescriptor.NONE); |
| if (monitor == null) |
| monitor= new NullProgressMonitor(); |
| try { |
| monitor.beginTask("", 100 * proxies.length); //$NON-NLS-1$ |
| connect(); |
| final List<RefactoringDescriptor> list= new ArrayList<>(proxies.length); |
| for (RefactoringDescriptorProxy proxy : proxies) { |
| final RefactoringDescriptor descriptor= proxy.requestDescriptor(new SubProgressMonitor(monitor, 100)); |
| if (descriptor != null) { |
| final int current= descriptor.getFlags(); |
| if ((current | flags) == current) |
| list.add(descriptor); |
| } |
| } |
| final RefactoringDescriptor[] descriptors= new RefactoringDescriptor[list.size()]; |
| list.toArray(descriptors); |
| RefactoringHistoryManager.writeRefactoringSession(stream, new RefactoringSessionDescriptor(descriptors, IRefactoringSerializationConstants.CURRENT_VERSION, null), time); |
| } finally { |
| disconnect(); |
| } |
| } |
| |
| @Override |
| public void writeRefactoringSession(final RefactoringSessionDescriptor descriptor, final OutputStream stream, final boolean time) throws CoreException { |
| Assert.isNotNull(descriptor); |
| Assert.isNotNull(stream); |
| RefactoringHistoryManager.writeRefactoringSession(stream, descriptor, time); |
| } |
| |
| /** |
| * Moves the project history from the old project to the new one. |
| * |
| * @param oldProject |
| * the old project, which does not exist anymore |
| * @param newProject |
| * the new project, which already exists |
| * @param monitor |
| * the progress monitor to use |
| */ |
| private void moveHistory(final IProject oldProject, final IProject newProject, final IProgressMonitor monitor) { |
| try { |
| monitor.beginTask(RefactoringCoreMessages.RefactoringHistoryService_updating_history, 60); |
| final IFileStore historyStore= EFS.getLocalFileSystem().getStore(RefactoringCorePlugin.getDefault().getStateLocation()).getChild(NAME_HISTORY_FOLDER); |
| final String oldName= oldProject.getName(); |
| final String newName= newProject.getName(); |
| final IFileStore oldStore= historyStore.getChild(oldName); |
| if (oldStore.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 10, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) { |
| final IFileStore newStore= historyStore.getChild(newName); |
| if (newStore.fetchInfo(EFS.NONE, new SubProgressMonitor(monitor, 10, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)).exists()) |
| newStore.delete(EFS.NONE, new SubProgressMonitor(monitor, 20, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| oldStore.move(newStore, EFS.OVERWRITE, new SubProgressMonitor(monitor, 20, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)); |
| } |
| } catch (CoreException exception) { |
| RefactoringCorePlugin.log(exception); |
| } finally { |
| monitor.done(); |
| } |
| } |
| |
| private void peformResourceChanged(final IResourceChangeEvent event) { |
| final int type= event.getType(); |
| if ((type & IResourceChangeEvent.POST_CHANGE) != 0) { |
| final IResourceDelta delta= event.getDelta(); |
| if (delta != null) { |
| final IResourceDelta[] deltas= delta.getAffectedChildren(); |
| if (deltas.length == 2) { |
| final IPath toPath= deltas[0].getMovedToPath(); |
| final IPath fromPath= deltas[1].getMovedFromPath(); |
| if (fromPath != null && toPath != null) { |
| final IResource oldResource= deltas[0].getResource(); |
| final IResource newResource= deltas[1].getResource(); |
| if (oldResource.getType() == IResource.PROJECT && newResource.getType() == IResource.PROJECT) |
| moveHistory((IProject) oldResource, (IProject) newResource, new NullProgressMonitor()); |
| } else { |
| if (deltas[0].getKind() == IResourceDelta.ADDED && deltas[1].getKind() == IResourceDelta.REMOVED) { |
| final IResource newResource= deltas[0].getResource(); |
| final IResource oldResource= deltas[1].getResource(); |
| if (oldResource.getType() == IResource.PROJECT && newResource.getType() == IResource.PROJECT) |
| moveHistory((IProject) oldResource, (IProject) newResource, new NullProgressMonitor()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private RefactoringDescriptor getRefactoringDescriptor(IUndoableOperation operation) { |
| if (operation instanceof TriggeredOperations) { |
| operation= ((TriggeredOperations) operation).getTriggeringOperation(); |
| } |
| if (operation instanceof UndoableOperation2ChangeAdapter) { |
| ChangeDescriptor changeDescriptor= ((UndoableOperation2ChangeAdapter) operation).getChangeDescriptor(); |
| if (changeDescriptor instanceof RefactoringChangeDescriptor) { |
| return ((RefactoringChangeDescriptor) changeDescriptor).getRefactoringDescriptor(); |
| } |
| } |
| return null; |
| } |
| |
| private void performHistoryNotification(final OperationHistoryEvent event) { |
| RefactoringDescriptor descriptor= getRefactoringDescriptor(event.getOperation()); |
| if (descriptor != null) { |
| RefactoringDescriptorProxyAdapter proxy= new RefactoringDescriptorProxyAdapter(descriptor); |
| switch (event.getEventType()) { |
| case OperationHistoryEvent.ABOUT_TO_EXECUTE: { |
| if (checkDescriptor(descriptor, event.getOperation())) { |
| fireRefactoringExecutionEvent(proxy, RefactoringExecutionEvent.ABOUT_TO_PERFORM); |
| } |
| break; |
| } |
| case OperationHistoryEvent.DONE: { |
| if (!RefactoringDescriptor.ID_UNKNOWN.equals(descriptor.getID())) { |
| long timeStamp= fOverrideTimeStamp >= 0 ? fOverrideTimeStamp : System.currentTimeMillis(); |
| descriptor.setTimeStamp(timeStamp); |
| } |
| |
| fireRefactoringHistoryEvent(proxy, RefactoringHistoryEvent.PUSHED); |
| fireRefactoringExecutionEvent(proxy, RefactoringExecutionEvent.PERFORMED); |
| break; |
| } |
| case OperationHistoryEvent.ABOUT_TO_UNDO: { |
| fireRefactoringExecutionEvent(proxy, RefactoringExecutionEvent.ABOUT_TO_UNDO); |
| break; |
| } |
| case OperationHistoryEvent.UNDONE: { |
| fireRefactoringHistoryEvent(proxy, RefactoringHistoryEvent.POPPED); |
| fireRefactoringExecutionEvent(proxy, RefactoringExecutionEvent.UNDONE); |
| break; |
| } |
| case OperationHistoryEvent.ABOUT_TO_REDO: { |
| fireRefactoringExecutionEvent(proxy, RefactoringExecutionEvent.ABOUT_TO_REDO); |
| break; |
| } |
| case OperationHistoryEvent.REDONE: { |
| fireRefactoringHistoryEvent(proxy, RefactoringHistoryEvent.PUSHED); |
| fireRefactoringExecutionEvent(proxy, RefactoringExecutionEvent.REDONE); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns the refactoring history manager corresponding to the project |
| * with the specified name. |
| * |
| * @param name |
| * the name of the project, or <code>null</code> for the |
| * workspace |
| * @return the refactoring history manager, or <code>null</code> |
| */ |
| private RefactoringHistoryManager getManager(final String name) { |
| final IFileStore store= EFS.getLocalFileSystem().getStore(RefactoringCorePlugin.getDefault().getStateLocation()).getChild(NAME_HISTORY_FOLDER); |
| if (name != null && !"".equals(name)) {//$NON-NLS-1$ |
| try { |
| final IProject project= ResourcesPlugin.getWorkspace().getRoot().getProject(name); |
| if (project.isAccessible()) { |
| if (hasSharedRefactoringHistory(project)) { |
| final URI uri= project.getLocationURI(); |
| if (uri != null) |
| return getManager(EFS.getStore(uri).getChild(RefactoringHistoryService.NAME_HISTORY_FOLDER), name); |
| } else |
| return getManager(store.getChild(name), name); |
| } |
| } catch (CoreException exception) { |
| // Do nothing |
| } |
| } else |
| return getManager(store.getChild(NAME_WORKSPACE_PROJECT), null); |
| return null; |
| } |
| |
| /** |
| * Returns the cached refactoring history manager for the specified |
| * history location. |
| * |
| * @param store |
| * the file store describing the history location |
| * @param name |
| * the non-empty project name, or <code>null</code> for the |
| * workspace |
| * @return the refactoring history manager |
| */ |
| private RefactoringHistoryManager getManager(final IFileStore store, final String name) { |
| Assert.isNotNull(store); |
| RefactoringHistoryManager manager= fManagerCache.get(store); |
| if (manager == null) { |
| manager= new RefactoringHistoryManager(store, name); |
| fManagerCache.put(store, manager); |
| } |
| return manager; |
| } |
| |
| } |