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.ltk.core.refactoring.resource;
15 
16 import org.eclipse.core.runtime.CoreException;
17 import org.eclipse.core.runtime.IProgressMonitor;
18 import org.eclipse.core.runtime.OperationCanceledException;
19 import org.eclipse.core.runtime.SubProgressMonitor;
20 
21 import org.eclipse.core.resources.IFile;
22 import org.eclipse.core.resources.IResource;
23 
24 import org.eclipse.core.filebuffers.FileBuffers;
25 import org.eclipse.core.filebuffers.ITextFileBuffer;
26 import org.eclipse.core.filebuffers.ITextFileBufferManager;
27 import org.eclipse.core.filebuffers.LocationKind;
28 
29 import org.eclipse.jface.text.IDocument;
30 import org.eclipse.jface.text.IDocumentExtension4;
31 
32 import org.eclipse.ltk.core.refactoring.Change;
33 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
34 import org.eclipse.ltk.internal.core.refactoring.BasicElementLabels;
35 import org.eclipse.ltk.internal.core.refactoring.Messages;
36 import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;
37 
38 /**
39  * Abstract change for resource based changes. The change controls the resource time stamp
40  * and read only state of the resource and makes sure it is not changed before executing the change.
41  *
42  * @since 3.4
43  */
44 public abstract class ResourceChange extends Change {
45 
46 	/**
47 	 * The default validation method. It tests the modified element for existence and makes sure it has not been modified
48 	 * since the change has been created.
49 	 */
50 	public static final int VALIDATE_DEFAULT= 0;
51 
52 	/**
53 	 * The 'not read only' validation method performs the default validations (see {@link #VALIDATE_DEFAULT}) and additionally ensures that the element
54 	 * is not read only.
55 	 */
56 	public static final int VALIDATE_NOT_READ_ONLY= 1 << 0;
57 
58 	/**
59 	 * The 'not dirty' validation method performs the default validations (see {@link #VALIDATE_DEFAULT}) and additionally ensures that the element
60 	 * does not contain unsaved modifications.
61 	 */
62 	public static final int VALIDATE_NOT_DIRTY= 1 << 1;
63 
64 	/**
65 	 * The 'save if dirty' validation method performs the default validations (see {@link #VALIDATE_DEFAULT}) and will
66 	 * save all unsaved modifications to the resource.
67 	 */
68 	public static final int SAVE_IF_DIRTY= 1 << 2;
69 
70 	private long fModificationStamp;
71 	private boolean fReadOnly;
72 	private int fValidationMethod;
73 
74 	/**
75 	 * Creates the resource change. The modification state will be
76 	 */
ResourceChange()77 	public ResourceChange() {
78 		fModificationStamp= IResource.NULL_STAMP;
79 		fReadOnly= false;
80 		fValidationMethod= VALIDATE_DEFAULT;
81 	}
82 
83 	/**
84 	 * Returns the resource of this change.
85 	 *
86 	 * @return the resource of this change
87 	 */
getModifiedResource()88 	protected abstract IResource getModifiedResource();
89 
90 	@Override
initializeValidationData(IProgressMonitor pm)91 	public void initializeValidationData(IProgressMonitor pm) {
92 		IResource resource= getModifiedResource();
93 		if (resource != null) {
94 			fModificationStamp= getModificationStamp(resource);
95 			fReadOnly= Resources.isReadOnly(resource);
96 		}
97 	}
98 
99 	/**
100 	 * Sets the validation methods used when the current resource is validated in {@link #isValid(IProgressMonitor)}.
101 	 * <p>
102 	 * By default the validation method is {@link #VALIDATE_DEFAULT}. Change implementors can add {@link #VALIDATE_NOT_DIRTY},
103 	 *  {@link #VALIDATE_NOT_READ_ONLY} or {@link #SAVE_IF_DIRTY}.
104 	 * </p>
105 	 *
106 	 * @param validationMethod the validation method used in {@link #isValid(IProgressMonitor)}.
107 	 * Supported validation methods currently are:
108 	 * <ul><li>{@link #VALIDATE_DEFAULT}</li>
109 	 * <li>{@link #VALIDATE_NOT_DIRTY}</li>
110 	 * <li>{@link #VALIDATE_NOT_READ_ONLY}</li>
111 	 * <li>{@link #SAVE_IF_DIRTY}</li>
112 	 * </ul>
113 	 * or combinations of these variables.
114 	 */
setValidationMethod(int validationMethod)115 	public void setValidationMethod(int validationMethod) {
116 		fValidationMethod= validationMethod;
117 	}
118 
119 	/**
120 	 * This implementation of {@link Change#isValid(IProgressMonitor)} tests the modified resource using the validation method
121 	 * specified by {@link #setValidationMethod(int)}.
122 	 */
123 	@Override
isValid(IProgressMonitor pm)124 	public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException, OperationCanceledException {
125 		pm.beginTask("", 2); //$NON-NLS-1$
126 		try {
127 			RefactoringStatus result= new RefactoringStatus();
128 			IResource resource= getModifiedResource();
129 			checkExistence(result, resource);
130 			if (result.hasFatalError())
131 				return result;
132 			if (fValidationMethod == VALIDATE_DEFAULT)
133 				return result;
134 
135 			ValidationState state= new ValidationState(resource);
136 			state.checkModificationStamp(result, fModificationStamp);
137 			if (result.hasFatalError())
138 				return result;
139 			state.checkSameReadOnly(result, fReadOnly);
140 			if (result.hasFatalError())
141 				return result;
142 			if ((fValidationMethod & VALIDATE_NOT_READ_ONLY) != 0) {
143 				state.checkReadOnly(result);
144 				if (result.hasFatalError())
145 					return result;
146 			}
147 			if ((fValidationMethod & SAVE_IF_DIRTY) != 0) {
148 				state.saveIfDirty(result, fModificationStamp, new SubProgressMonitor(pm, 1));
149 			}
150 			if ((fValidationMethod & VALIDATE_NOT_DIRTY) != 0) {
151 				state.checkDirty(result);
152 			}
153 			return result;
154 		} finally {
155 			pm.done();
156 		}
157 	}
158 
159 	/**
160 	 * Utility method to validate a resource to be modified.
161 	 *
162 	 * @param result the status where the result will be added to
163 	 * @param resource the resource to validate
164 	 * @param validationMethod the validation method used in {@link #isValid(IProgressMonitor)}.
165 	 * Supported validation methods currently are:
166 	 * <ul><li>{@link #VALIDATE_DEFAULT}</li>
167 	 * <li>{@link #VALIDATE_NOT_DIRTY}</li>
168 	 * <li>{@link #VALIDATE_NOT_READ_ONLY}</li>
169 	 * <li>{@link #SAVE_IF_DIRTY}</li>
170 	 * </ul>
171 	 * or combinations of these methods.
172 	 */
checkIfModifiable(RefactoringStatus result, IResource resource, int validationMethod)173 	protected static void checkIfModifiable(RefactoringStatus result, IResource resource, int validationMethod) {
174 		checkExistence(result, resource);
175 		if (result.hasFatalError())
176 			return;
177 		if (validationMethod == VALIDATE_DEFAULT)
178 			return;
179 		ValidationState state= new ValidationState(resource);
180 		if ((validationMethod & VALIDATE_NOT_READ_ONLY) != 0) {
181 			state.checkReadOnly(result);
182 			if (result.hasFatalError())
183 				return;
184 		}
185 		if ((validationMethod & VALIDATE_NOT_DIRTY) != 0) {
186 			state.checkDirty(result);
187 		}
188 	}
189 
checkExistence(RefactoringStatus status, IResource element)190 	private static void checkExistence(RefactoringStatus status, IResource element) {
191 		if (element == null) {
192 			status.addFatalError(RefactoringCoreMessages.ResourceChange_error_no_input);
193 		} else if (!element.exists()) {
194 			status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_does_not_exist, BasicElementLabels.getPathLabel(element.getFullPath(), false)));
195 		}
196 	}
197 
198 	@Override
getModifiedElement()199 	public Object getModifiedElement() {
200 		return getModifiedResource();
201 	}
202 
203 
204 	@Override
toString()205 	public String toString() {
206 		return getName();
207 	}
208 
getModificationStamp(IResource resource)209 	private long getModificationStamp(IResource resource) {
210 		if (!(resource instanceof IFile))
211 			return resource.getModificationStamp();
212 		IFile file= (IFile)resource;
213 		ITextFileBuffer buffer= getBuffer(file);
214 		if (buffer == null) {
215 			return file.getModificationStamp();
216 		} else {
217 			IDocument document= buffer.getDocument();
218 			if (document instanceof IDocumentExtension4) {
219 				return ((IDocumentExtension4)document).getModificationStamp();
220 			} else {
221 				return file.getModificationStamp();
222 			}
223 		}
224 	}
225 
getBuffer(IFile file)226 	private static ITextFileBuffer getBuffer(IFile file) {
227 		ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
228 		return manager.getTextFileBuffer(file.getFullPath(), LocationKind.IFILE);
229 	}
230 
231 	private static class ValidationState {
232 		private IResource fResource;
233 		private int fKind;
234 		private boolean fDirty;
235 		private boolean fReadOnly;
236 		private long fModificationStamp;
237 		private ITextFileBuffer fTextFileBuffer;
238 		public static final int RESOURCE= 1;
239 		public static final int DOCUMENT= 2;
240 
ValidationState(IResource resource)241 		public ValidationState(IResource resource) {
242 			fResource= resource;
243 			if (resource instanceof IFile) {
244 				initializeFile((IFile) resource);
245 			} else {
246 				initializeResource(resource);
247 			}
248 		}
249 
saveIfDirty(RefactoringStatus status, long stampToMatch, IProgressMonitor pm)250 		public void saveIfDirty(RefactoringStatus status, long stampToMatch, IProgressMonitor pm) throws CoreException {
251 			if (fDirty) {
252 				if (fKind == DOCUMENT && fTextFileBuffer != null && stampToMatch == fModificationStamp) {
253 					fTextFileBuffer.commit(pm, false);
254 				} else {
255 					status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_unsaved, BasicElementLabels.getPathLabel(fResource.getFullPath(), false)));
256 				}
257 			}
258 		}
259 
checkDirty(RefactoringStatus status)260 		public void checkDirty(RefactoringStatus status) {
261 			if (fDirty) {
262 				status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_unsaved, BasicElementLabels.getPathLabel(fResource.getFullPath(), false)));
263 			}
264 		}
265 
checkReadOnly(RefactoringStatus status)266 		public void checkReadOnly(RefactoringStatus status) {
267 			if (fReadOnly) {
268 				status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_read_only, BasicElementLabels.getPathLabel(fResource.getFullPath(), false)));
269 			}
270 		}
271 
checkSameReadOnly(RefactoringStatus status, boolean valueToMatch)272 		public void checkSameReadOnly(RefactoringStatus status, boolean valueToMatch) {
273 			if (fReadOnly != valueToMatch) {
274 				status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_read_only_state_changed, BasicElementLabels.getPathLabel(fResource.getFullPath(), false)));
275 			}
276 		}
277 
checkModificationStamp(RefactoringStatus status, long stampToMatch)278 		public void checkModificationStamp(RefactoringStatus status, long stampToMatch) {
279 			if (fKind == DOCUMENT) {
280 				if (stampToMatch != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP && fModificationStamp != stampToMatch) {
281 					status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_has_been_modified, BasicElementLabels.getPathLabel(fResource.getFullPath(), false)));
282 				}
283 			} else {
284 				if (stampToMatch != IResource.NULL_STAMP && fModificationStamp != stampToMatch) {
285 					status.addFatalError(Messages.format(RefactoringCoreMessages.ResourceChange_error_has_been_modified, BasicElementLabels.getPathLabel(fResource.getFullPath(), false)));
286 				}
287 			}
288 		}
289 
initializeFile(IFile file)290 		private void initializeFile(IFile file) {
291 			fTextFileBuffer= getBuffer(file);
292 			if (fTextFileBuffer == null) {
293 				initializeResource(file);
294 			} else {
295 				IDocument document= fTextFileBuffer.getDocument();
296 				fDirty= fTextFileBuffer.isDirty();
297 				fReadOnly= Resources.isReadOnly(file);
298 				if (document instanceof IDocumentExtension4) {
299 					fKind= DOCUMENT;
300 					fModificationStamp= ((IDocumentExtension4) document).getModificationStamp();
301 				} else {
302 					fKind= RESOURCE;
303 					fModificationStamp= file.getModificationStamp();
304 				}
305 			}
306 
307 		}
308 
initializeResource(IResource resource)309 		private void initializeResource(IResource resource) {
310 			fKind= RESOURCE;
311 			fDirty= false;
312 			fReadOnly= Resources.isReadOnly(resource);
313 			fModificationStamp= resource.getModificationStamp();
314 		}
315 	}
316 }
317