/******************************************************************************* * Copyright (c) 2007, 2018 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.core.filebuffers.manipulation; import java.util.ArrayList; import org.eclipse.core.internal.filebuffers.FileBuffersPlugin; 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.NullProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.IJobManager; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.MultiRule; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.IFileBuffer; import org.eclipse.core.filebuffers.IFileBufferManager; import org.eclipse.core.filebuffers.IFileBufferStatusCodes; import org.eclipse.core.filebuffers.ITextFileBuffer; import org.eclipse.core.filebuffers.ITextFileBufferManager; import org.eclipse.core.filebuffers.LocationKind; /** * A GenericFileBufferOperationRunner executes * {@link org.eclipse.core.filebuffers.manipulation.IFileBufferOperation}. * The runner takes care of all aspects that are not operation specific. *

* This class is not intended to be subclassed. Clients instantiate this class. *

* * @see org.eclipse.core.filebuffers.manipulation.IFileBufferOperation * @since 3.3 * @noextend This class is not intended to be subclassed by clients. */ public class GenericFileBufferOperationRunner { /** The validation context */ private final Object fValidationContext; /** The file buffer manager */ private final IFileBufferManager fFileBufferManager; /** The lock for waiting for completion of computation in the UI thread. */ private final Object fCompletionLock= new Object(); /** The flag indicating completion of computation in the UI thread. */ private transient boolean fIsCompleted; /** The exception thrown during the computation in the UI thread. */ private transient Throwable fThrowable; /** * Creates a new file buffer operation runner. * * @param fileBufferManager the file buffer manager * @param validationContext the validationContext */ public GenericFileBufferOperationRunner(IFileBufferManager fileBufferManager, Object validationContext) { fFileBufferManager= fileBufferManager; fValidationContext= validationContext; } /** * Executes the given operation for all file buffers specified by the given locations. * * @param locations the file buffer locations * @param operation the operation to be performed * @param monitor the progress monitor, or null if progress reporting is not desired * @throws CoreException in case of error * @throws OperationCanceledException in case the execution get canceled */ public void execute(IPath[] locations, final IFileBufferOperation operation, IProgressMonitor monitor) throws CoreException, OperationCanceledException { final int size= locations.length; SubMonitor subMonitor= SubMonitor.convert(monitor, operation.getOperationName(), size * 200); try { IFileBuffer[] fileBuffers= createFileBuffers(locations, subMonitor.split(size * 10)); IFileBuffer[] fileBuffers2Save= findFileBuffersToSave(fileBuffers); fFileBufferManager.validateState(fileBuffers2Save, subMonitor.split(size * 10), fValidationContext); if (!isCommitable(fileBuffers2Save)) { throw new OperationCanceledException(); } IFileBuffer[] unsynchronizedFileBuffers= findUnsynchronizedFileBuffers(fileBuffers); performOperation(unsynchronizedFileBuffers, operation, subMonitor.split(size * 40)); final IFileBuffer[] synchronizedFileBuffers= findSynchronizedFileBuffers(fileBuffers); fIsCompleted= false; fThrowable= null; synchronized (fCompletionLock) { executeInContext(() -> { synchronized (fCompletionLock) { try { SafeRunner.run(new ISafeRunnable() { @Override public void handleException(Throwable throwable) { fThrowable= throwable; } @Override public void run() throws Exception { performOperation(synchronizedFileBuffers, operation, subMonitor.split(50)); } }); } finally { fIsCompleted= true; fCompletionLock.notifyAll(); } } }); while (!fIsCompleted) { try { fCompletionLock.wait(500); } catch (InterruptedException x) { } } } if (fThrowable != null) { if (fThrowable instanceof CoreException) throw (CoreException) fThrowable; throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CONTENT_CHANGE_FAILED, fThrowable.getLocalizedMessage(), fThrowable)); } commit(fileBuffers2Save, subMonitor.split(size * 80)); } finally { releaseFileBuffers(locations, subMonitor.split(size * 10)); } } private void performOperation(IFileBuffer fileBuffer, IFileBufferOperation operation, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException { SubMonitor subMonitor= SubMonitor.convert(progressMonitor, 100); ISchedulingRule rule= fileBuffer.computeCommitRule(); IJobManager manager= Job.getJobManager(); manager.beginRule(rule, subMonitor.split(1)); String name= fileBuffer.getLocation().lastSegment(); subMonitor.setTaskName(name); operation.run(fileBuffer, subMonitor.split(99)); manager.endRule(rule); } private void performOperation(IFileBuffer[] fileBuffers, IFileBufferOperation operation, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException { SubMonitor subMonitor= SubMonitor.convert(progressMonitor, fileBuffers.length); for (IFileBuffer fileBuffer : fileBuffers) { performOperation(fileBuffer, operation, subMonitor.split(1)); } } private void executeInContext(Runnable runnable) { ITextFileBufferManager fileBufferManager= FileBuffers.getTextFileBufferManager(); fileBufferManager.execute(runnable); } private IFileBuffer[] findUnsynchronizedFileBuffers(IFileBuffer[] fileBuffers) { ArrayList list= new ArrayList<>(); for (int i= 0; i < fileBuffers.length; i++) { if (!fileBuffers[i].isSynchronizationContextRequested()) list.add(fileBuffers[i]); } return list.toArray(new IFileBuffer[list.size()]); } private IFileBuffer[] findSynchronizedFileBuffers(IFileBuffer[] fileBuffers) { ArrayList list= new ArrayList<>(); for (IFileBuffer fileBuffer : fileBuffers) { if (fileBuffer.isSynchronizationContextRequested()) list.add(fileBuffer); } return list.toArray(new IFileBuffer[list.size()]); } private IFileBuffer[] createFileBuffers(IPath[] locations, IProgressMonitor progressMonitor) throws CoreException { SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.FileBufferOperationRunner_task_connecting, locations.length); try { IFileBuffer[] fileBuffers= new ITextFileBuffer[locations.length]; for (int i= 0; i < locations.length; i++) { fFileBufferManager.connect(locations[i], LocationKind.NORMALIZE, subMonitor.split(1)); fileBuffers[i]= fFileBufferManager.getFileBuffer(locations[i], LocationKind.NORMALIZE); } return fileBuffers; } catch (CoreException x) { try { releaseFileBuffers(locations, new NullProgressMonitor()); } catch (CoreException e) { } throw x; } } private void releaseFileBuffers(IPath[] locations, IProgressMonitor progressMonitor) throws CoreException { SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.FileBufferOperationRunner_task_disconnecting, locations.length); final ITextFileBufferManager fileBufferManager= FileBuffers.getTextFileBufferManager(); for (IPath location : locations) { fileBufferManager.disconnect(location, LocationKind.NORMALIZE, subMonitor.split(1)); } } private IFileBuffer[] findFileBuffersToSave(IFileBuffer[] fileBuffers) { ArrayList list= new ArrayList<>(); for (IFileBuffer fileBuffer : fileBuffers) { IFileBuffer buffer= fileBuffer; if (!buffer.isDirty()) list.add(buffer); } return list.toArray(new IFileBuffer[list.size()]); } private boolean isCommitable(IFileBuffer[] fileBuffers) { for (int i= 0; i < fileBuffers.length; i++) { if (!fileBuffers[i].isCommitable()) return false; } return true; } protected ISchedulingRule computeCommitRule(IFileBuffer[] fileBuffers) { ArrayList list= new ArrayList<>(); for (IFileBuffer fileBuffer : fileBuffers) { ISchedulingRule rule= fileBuffer.computeCommitRule(); if (rule != null) list.add(rule); } ISchedulingRule[] rules= new ISchedulingRule[list.size()]; list.toArray(rules); return new MultiRule(rules); } protected void commit(final IFileBuffer[] fileBuffers, final IProgressMonitor progressMonitor) throws CoreException { SubMonitor subMonitor= SubMonitor.convert(progressMonitor, 2); ISchedulingRule rule= computeCommitRule(fileBuffers); Job.getJobManager().beginRule(rule, subMonitor.split(1)); try { doCommit(fileBuffers, subMonitor.split(1)); } finally { Job.getJobManager().endRule(rule); } } protected void doCommit(final IFileBuffer[] fileBuffers, IProgressMonitor progressMonitor) throws CoreException { SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.FileBufferOperationRunner_task_committing, fileBuffers.length); for (IFileBuffer fileBuffer : fileBuffers) { fileBuffer.commit(subMonitor.split(1), true); } } }