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