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