1 /******************************************************************************* 2 * Copyright (c) 2007, 2018 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.core.filebuffers.manipulation; 15 16 import java.util.ArrayList; 17 18 import org.eclipse.core.internal.filebuffers.FileBuffersPlugin; 19 20 import org.eclipse.core.runtime.CoreException; 21 import org.eclipse.core.runtime.IPath; 22 import org.eclipse.core.runtime.IProgressMonitor; 23 import org.eclipse.core.runtime.ISafeRunnable; 24 import org.eclipse.core.runtime.IStatus; 25 import org.eclipse.core.runtime.NullProgressMonitor; 26 import org.eclipse.core.runtime.OperationCanceledException; 27 import org.eclipse.core.runtime.SafeRunner; 28 import org.eclipse.core.runtime.Status; 29 import org.eclipse.core.runtime.SubMonitor; 30 import org.eclipse.core.runtime.jobs.IJobManager; 31 import org.eclipse.core.runtime.jobs.ISchedulingRule; 32 import org.eclipse.core.runtime.jobs.Job; 33 import org.eclipse.core.runtime.jobs.MultiRule; 34 35 import org.eclipse.core.filebuffers.FileBuffers; 36 import org.eclipse.core.filebuffers.IFileBuffer; 37 import org.eclipse.core.filebuffers.IFileBufferManager; 38 import org.eclipse.core.filebuffers.IFileBufferStatusCodes; 39 import org.eclipse.core.filebuffers.ITextFileBuffer; 40 import org.eclipse.core.filebuffers.ITextFileBufferManager; 41 import org.eclipse.core.filebuffers.LocationKind; 42 43 44 /** 45 * A <code>GenericFileBufferOperationRunner</code> executes 46 * {@link org.eclipse.core.filebuffers.manipulation.IFileBufferOperation}. 47 * The runner takes care of all aspects that are not operation specific. 48 * <p> 49 * This class is not intended to be subclassed. Clients instantiate this class. 50 * </p> 51 * 52 * @see org.eclipse.core.filebuffers.manipulation.IFileBufferOperation 53 * @since 3.3 54 * @noextend This class is not intended to be subclassed by clients. 55 */ 56 public class GenericFileBufferOperationRunner { 57 58 /** The validation context */ 59 private final Object fValidationContext; 60 /** The file buffer manager */ 61 private final IFileBufferManager fFileBufferManager; 62 63 /** The lock for waiting for completion of computation in the UI thread. */ 64 private final Object fCompletionLock= new Object(); 65 /** The flag indicating completion of computation in the UI thread. */ 66 private transient boolean fIsCompleted; 67 /** The exception thrown during the computation in the UI thread. */ 68 private transient Throwable fThrowable; 69 70 71 /** 72 * Creates a new file buffer operation runner. 73 * 74 * @param fileBufferManager the file buffer manager 75 * @param validationContext the validationContext 76 */ GenericFileBufferOperationRunner(IFileBufferManager fileBufferManager, Object validationContext)77 public GenericFileBufferOperationRunner(IFileBufferManager fileBufferManager, Object validationContext) { 78 fFileBufferManager= fileBufferManager; 79 fValidationContext= validationContext; 80 } 81 82 /** 83 * Executes the given operation for all file buffers specified by the given locations. 84 * 85 * @param locations the file buffer locations 86 * @param operation the operation to be performed 87 * @param monitor the progress monitor, or <code>null</code> if progress reporting is not desired 88 * @throws CoreException in case of error 89 * @throws OperationCanceledException in case the execution get canceled 90 */ execute(IPath[] locations, final IFileBufferOperation operation, IProgressMonitor monitor)91 public void execute(IPath[] locations, final IFileBufferOperation operation, IProgressMonitor monitor) throws CoreException, OperationCanceledException { 92 final int size= locations.length; 93 SubMonitor subMonitor= SubMonitor.convert(monitor, operation.getOperationName(), size * 200); 94 try { 95 IFileBuffer[] fileBuffers= createFileBuffers(locations, subMonitor.split(size * 10)); 96 97 IFileBuffer[] fileBuffers2Save= findFileBuffersToSave(fileBuffers); 98 fFileBufferManager.validateState(fileBuffers2Save, subMonitor.split(size * 10), fValidationContext); 99 if (!isCommitable(fileBuffers2Save)) 100 { 101 throw new OperationCanceledException(); 102 } 103 104 IFileBuffer[] unsynchronizedFileBuffers= findUnsynchronizedFileBuffers(fileBuffers); 105 performOperation(unsynchronizedFileBuffers, operation, subMonitor.split(size * 40)); 106 107 final IFileBuffer[] synchronizedFileBuffers= findSynchronizedFileBuffers(fileBuffers); 108 fIsCompleted= false; 109 fThrowable= null; 110 synchronized (fCompletionLock) { 111 112 executeInContext(() -> { 113 synchronized (fCompletionLock) { 114 try { 115 SafeRunner.run(new ISafeRunnable() { 116 @Override 117 public void handleException(Throwable throwable) { 118 fThrowable= throwable; 119 } 120 121 @Override 122 public void run() throws Exception { 123 performOperation(synchronizedFileBuffers, operation, subMonitor.split(50)); 124 } 125 }); 126 } finally { 127 fIsCompleted= true; 128 fCompletionLock.notifyAll(); 129 } 130 } 131 }); 132 133 while (!fIsCompleted) { 134 try { 135 fCompletionLock.wait(500); 136 } catch (InterruptedException x) { 137 } 138 } 139 } 140 141 if (fThrowable != null) { 142 if (fThrowable instanceof CoreException) 143 throw (CoreException) fThrowable; 144 throw new CoreException(new Status(IStatus.ERROR, FileBuffersPlugin.PLUGIN_ID, IFileBufferStatusCodes.CONTENT_CHANGE_FAILED, fThrowable.getLocalizedMessage(), fThrowable)); 145 } 146 147 commit(fileBuffers2Save, subMonitor.split(size * 80)); 148 149 } finally { 150 releaseFileBuffers(locations, subMonitor.split(size * 10)); 151 } 152 } 153 performOperation(IFileBuffer fileBuffer, IFileBufferOperation operation, IProgressMonitor progressMonitor)154 private void performOperation(IFileBuffer fileBuffer, IFileBufferOperation operation, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException { 155 SubMonitor subMonitor= SubMonitor.convert(progressMonitor, 100); 156 ISchedulingRule rule= fileBuffer.computeCommitRule(); 157 IJobManager manager= Job.getJobManager(); 158 manager.beginRule(rule, subMonitor.split(1)); 159 String name= fileBuffer.getLocation().lastSegment(); 160 subMonitor.setTaskName(name); 161 operation.run(fileBuffer, subMonitor.split(99)); 162 manager.endRule(rule); 163 } 164 performOperation(IFileBuffer[] fileBuffers, IFileBufferOperation operation, IProgressMonitor progressMonitor)165 private void performOperation(IFileBuffer[] fileBuffers, IFileBufferOperation operation, IProgressMonitor progressMonitor) throws CoreException, OperationCanceledException { 166 SubMonitor subMonitor= SubMonitor.convert(progressMonitor, fileBuffers.length); 167 for (IFileBuffer fileBuffer : fileBuffers) { 168 performOperation(fileBuffer, operation, subMonitor.split(1)); 169 } 170 } 171 executeInContext(Runnable runnable)172 private void executeInContext(Runnable runnable) { 173 ITextFileBufferManager fileBufferManager= FileBuffers.getTextFileBufferManager(); 174 fileBufferManager.execute(runnable); 175 } 176 findUnsynchronizedFileBuffers(IFileBuffer[] fileBuffers)177 private IFileBuffer[] findUnsynchronizedFileBuffers(IFileBuffer[] fileBuffers) { 178 ArrayList<IFileBuffer> list= new ArrayList<>(); 179 for (IFileBuffer fileBuffer : fileBuffers) { 180 if (!fileBuffer.isSynchronizationContextRequested()) { 181 list.add(fileBuffer); 182 } 183 } 184 return list.toArray(new IFileBuffer[list.size()]); 185 } 186 findSynchronizedFileBuffers(IFileBuffer[] fileBuffers)187 private IFileBuffer[] findSynchronizedFileBuffers(IFileBuffer[] fileBuffers) { 188 ArrayList<IFileBuffer> list= new ArrayList<>(); 189 for (IFileBuffer fileBuffer : fileBuffers) { 190 if (fileBuffer.isSynchronizationContextRequested()) 191 list.add(fileBuffer); 192 } 193 return list.toArray(new IFileBuffer[list.size()]); 194 } 195 createFileBuffers(IPath[] locations, IProgressMonitor progressMonitor)196 private IFileBuffer[] createFileBuffers(IPath[] locations, IProgressMonitor progressMonitor) throws CoreException { 197 198 SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.FileBufferOperationRunner_task_connecting, locations.length); 199 try { 200 IFileBuffer[] fileBuffers= new ITextFileBuffer[locations.length]; 201 for (int i= 0; i < locations.length; i++) { 202 fFileBufferManager.connect(locations[i], LocationKind.NORMALIZE, subMonitor.split(1)); 203 fileBuffers[i]= fFileBufferManager.getFileBuffer(locations[i], LocationKind.NORMALIZE); 204 } 205 return fileBuffers; 206 207 } catch (CoreException x) { 208 try { 209 releaseFileBuffers(locations, new NullProgressMonitor()); 210 } catch (CoreException e) { 211 } 212 throw x; 213 } 214 } 215 releaseFileBuffers(IPath[] locations, IProgressMonitor progressMonitor)216 private void releaseFileBuffers(IPath[] locations, IProgressMonitor progressMonitor) throws CoreException { 217 SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.FileBufferOperationRunner_task_disconnecting, locations.length); 218 final ITextFileBufferManager fileBufferManager= FileBuffers.getTextFileBufferManager(); 219 for (IPath location : locations) { 220 fileBufferManager.disconnect(location, LocationKind.NORMALIZE, subMonitor.split(1)); 221 } 222 } 223 findFileBuffersToSave(IFileBuffer[] fileBuffers)224 private IFileBuffer[] findFileBuffersToSave(IFileBuffer[] fileBuffers) { 225 ArrayList<IFileBuffer> list= new ArrayList<>(); 226 for (IFileBuffer fileBuffer : fileBuffers) { 227 IFileBuffer buffer= fileBuffer; 228 if (!buffer.isDirty()) 229 list.add(buffer); 230 } 231 return list.toArray(new IFileBuffer[list.size()]); 232 } 233 isCommitable(IFileBuffer[] fileBuffers)234 private boolean isCommitable(IFileBuffer[] fileBuffers) { 235 for (IFileBuffer fileBuffer : fileBuffers) { 236 if (!fileBuffer.isCommitable()) { 237 return false; 238 } 239 } 240 return true; 241 } 242 computeCommitRule(IFileBuffer[] fileBuffers)243 protected ISchedulingRule computeCommitRule(IFileBuffer[] fileBuffers) { 244 ArrayList<ISchedulingRule> list= new ArrayList<>(); 245 for (IFileBuffer fileBuffer : fileBuffers) { 246 ISchedulingRule rule= fileBuffer.computeCommitRule(); 247 if (rule != null) 248 list.add(rule); 249 } 250 ISchedulingRule[] rules= new ISchedulingRule[list.size()]; 251 list.toArray(rules); 252 return new MultiRule(rules); 253 } 254 commit(final IFileBuffer[] fileBuffers, final IProgressMonitor progressMonitor)255 protected void commit(final IFileBuffer[] fileBuffers, final IProgressMonitor progressMonitor) throws CoreException { 256 SubMonitor subMonitor= SubMonitor.convert(progressMonitor, 2); 257 ISchedulingRule rule= computeCommitRule(fileBuffers); 258 Job.getJobManager().beginRule(rule, subMonitor.split(1)); 259 try { 260 doCommit(fileBuffers, subMonitor.split(1)); 261 } finally { 262 Job.getJobManager().endRule(rule); 263 } 264 } 265 doCommit(final IFileBuffer[] fileBuffers, IProgressMonitor progressMonitor)266 protected void doCommit(final IFileBuffer[] fileBuffers, IProgressMonitor progressMonitor) throws CoreException { 267 SubMonitor subMonitor= SubMonitor.convert(progressMonitor, FileBuffersMessages.FileBufferOperationRunner_task_committing, fileBuffers.length); 268 for (IFileBuffer fileBuffer : fileBuffers) { 269 fileBuffer.commit(subMonitor.split(1), true); 270 } 271 } 272 273 } 274