| 1 11 package org.eclipse.ui.editors.text; 12 13 import java.io.ByteArrayInputStream ; 14 import java.io.IOException ; 15 import java.io.InputStream ; 16 import java.io.Reader ; 17 import java.io.SequenceInputStream ; 18 import java.nio.ByteBuffer ; 19 import java.nio.CharBuffer ; 20 import java.nio.charset.CharacterCodingException ; 21 import java.nio.charset.Charset ; 22 import java.nio.charset.CharsetEncoder ; 23 import java.nio.charset.CodingErrorAction ; 24 import java.nio.charset.IllegalCharsetNameException ; 25 import java.nio.charset.UnmappableCharacterException ; 26 import java.nio.charset.UnsupportedCharsetException ; 27 28 import org.eclipse.swt.widgets.Display; 29 30 import org.eclipse.core.runtime.Assert; 31 import org.eclipse.core.runtime.CoreException; 32 import org.eclipse.core.runtime.IPath; 33 import org.eclipse.core.runtime.IProgressMonitor; 34 import org.eclipse.core.runtime.IStatus; 35 import org.eclipse.core.runtime.MultiStatus; 36 import org.eclipse.core.runtime.OperationCanceledException; 37 import org.eclipse.core.runtime.Platform; 38 import org.eclipse.core.runtime.QualifiedName; 39 import org.eclipse.core.runtime.Status; 40 import org.eclipse.core.runtime.SubProgressMonitor; 41 import org.eclipse.core.runtime.content.IContentDescription; 42 import org.eclipse.core.runtime.content.IContentType; 43 import org.eclipse.core.runtime.jobs.ISchedulingRule; 44 import org.eclipse.core.runtime.preferences.IScopeContext; 45 import org.eclipse.core.runtime.preferences.InstanceScope; 46 47 import org.eclipse.core.resources.IFile; 48 import org.eclipse.core.resources.IResource; 49 import org.eclipse.core.resources.IResourceChangeEvent; 50 import org.eclipse.core.resources.IResourceChangeListener; 51 import org.eclipse.core.resources.IResourceDelta; 52 import org.eclipse.core.resources.IResourceDeltaVisitor; 53 import org.eclipse.core.resources.IResourceRuleFactory; 54 import org.eclipse.core.resources.IResourceStatus; 55 import org.eclipse.core.resources.IWorkspace; 56 import org.eclipse.core.resources.ProjectScope; 57 import org.eclipse.core.resources.ResourcesPlugin; 58 59 import org.eclipse.core.filebuffers.manipulation.ContainerCreator; 60 61 import org.eclipse.jface.operation.IRunnableContext; 62 63 import org.eclipse.jface.text.IDocument; 64 import org.eclipse.jface.text.IDocumentExtension4; 65 import org.eclipse.jface.text.source.IAnnotationModel; 66 67 import org.eclipse.ui.IEditorInput; 68 import org.eclipse.ui.IFileEditorInput; 69 import org.eclipse.ui.IWorkbench; 70 import org.eclipse.ui.IWorkbenchWindow; 71 import org.eclipse.ui.PlatformUI; 72 import org.eclipse.ui.internal.editors.text.NLSUtility; 73 import org.eclipse.ui.internal.editors.text.WorkspaceOperationRunner; 74 import org.eclipse.ui.part.FileEditorInput; 75 import org.eclipse.ui.texteditor.AbstractMarkerAnnotationModel; 76 import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel; 77 78 79 84 public class FileDocumentProvider extends StorageDocumentProvider { 85 86 91 private static final QualifiedName ENCODING_KEY = new QualifiedName(EditorsUI.PLUGIN_ID, "encoding"); 96 private static final String CHARSET_UTF_8= "UTF-8"; 98 99 103 private WorkspaceOperationRunner fOperationRunner; 104 108 private IResourceRuleFactory fResourceRuleFactory; 109 110 117 protected class SafeChange implements Runnable { 118 119 120 private IFileEditorInput fInput; 121 122 127 public SafeChange(IFileEditorInput input) { 128 fInput= input; 129 } 130 131 138 protected void execute(IFileEditorInput input) throws Exception { 139 } 140 141 144 public void run() { 145 146 if (getElementInfo(fInput) == null) { 147 fireElementStateChangeFailed(fInput); 148 return; 149 } 150 151 try { 152 execute(fInput); 153 } catch (Exception e) { 154 fireElementStateChangeFailed(fInput); 155 } 156 } 157 } 158 159 160 163 protected class FileSynchronizer implements IResourceChangeListener, IResourceDeltaVisitor { 164 165 166 protected IFileEditorInput fFileEditorInput; 167 172 protected boolean fIsInstalled= false; 173 174 179 public FileSynchronizer(IFileEditorInput fileEditorInput) { 180 fFileEditorInput= fileEditorInput; 181 } 182 183 189 public FileSynchronizer(FileEditorInput fileEditorInput) { 190 fFileEditorInput= fileEditorInput; 191 } 192 193 198 protected IFile getFile() { 199 return fFileEditorInput.getFile(); 200 } 201 202 205 public void install() { 206 getFile().getWorkspace().addResourceChangeListener(this); 207 fIsInstalled= true; 208 } 209 210 213 public void uninstall() { 214 getFile().getWorkspace().removeResourceChangeListener(this); 215 fIsInstalled= false; 216 } 217 218 221 public void resourceChanged(IResourceChangeEvent e) { 222 IResourceDelta delta= e.getDelta(); 223 try { 224 if (delta != null && fIsInstalled) 225 delta.accept(this); 226 } catch (CoreException x) { 227 handleCoreException(x, "FileDocumentProvider.resourceChanged"); } 229 } 230 231 234 public boolean visit(IResourceDelta delta) throws CoreException { 235 if (delta == null) 236 return false; 237 238 delta= delta.findMember(getFile().getFullPath()); 239 240 if (delta == null) 241 return false; 242 243 Runnable runnable= null; 244 245 switch (delta.getKind()) { 246 case IResourceDelta.CHANGED: 247 FileInfo info= (FileInfo) getElementInfo(fFileEditorInput); 248 if (info == null || info.fCanBeSaved) 249 break; 250 251 boolean isSynchronized= computeModificationStamp(getFile()) == info.fModificationStamp; 252 if ((IResourceDelta.ENCODING & delta.getFlags()) != 0 && isSynchronized) { 253 runnable= new SafeChange(fFileEditorInput) { 254 protected void execute(IFileEditorInput input) throws Exception { 255 handleElementContentChanged(input); 256 } 257 }; 258 } 259 260 if (runnable == null && (IResourceDelta.CONTENT & delta.getFlags()) != 0 && !isSynchronized) { 261 runnable= new SafeChange(fFileEditorInput) { 262 protected void execute(IFileEditorInput input) throws Exception { 263 handleElementContentChanged(input); 264 } 265 }; 266 } 267 break; 268 269 case IResourceDelta.REMOVED: 270 if ((IResourceDelta.MOVED_TO & delta.getFlags()) != 0) { 271 final IPath path= delta.getMovedToPath(); 272 runnable= new SafeChange(fFileEditorInput) { 273 protected void execute(IFileEditorInput input) throws Exception { 274 handleElementMoved(input, path); 275 } 276 }; 277 } else { 278 info= (FileInfo) getElementInfo(fFileEditorInput); 279 if (info != null && !info.fCanBeSaved) { 280 runnable= new SafeChange(fFileEditorInput) { 281 protected void execute(IFileEditorInput input) throws Exception { 282 handleElementDeleted(input); 283 } 284 }; 285 } 286 } 287 break; 288 } 289 290 if (runnable != null) 291 update(runnable); 292 293 return false; 294 } 295 296 301 protected void update(Runnable runnable) { 302 303 if (runnable instanceof SafeChange) 304 fireElementStateChanging(fFileEditorInput); 305 306 IWorkbench workbench= PlatformUI.getWorkbench(); 307 IWorkbenchWindow[] windows= workbench.getWorkbenchWindows(); 308 if (windows != null && windows.length > 0) { 309 Display display= windows[0].getShell().getDisplay(); 310 display.asyncExec(runnable); 311 } else { 312 runnable.run(); 313 } 314 } 315 } 316 317 318 319 322 protected class FileInfo extends StorageInfo { 323 324 325 public FileSynchronizer fFileSynchronizer; 326 327 public long fModificationStamp= IResource.NULL_STAMP; 328 331 private boolean fHasBOM; 332 333 340 public FileInfo(IDocument document, IAnnotationModel model, FileSynchronizer fileSynchronizer) { 341 super(document, model); 342 fFileSynchronizer= fileSynchronizer; 343 } 344 } 345 346 347 350 public FileDocumentProvider() { 351 super(); 352 fResourceRuleFactory= ResourcesPlugin.getWorkspace().getRuleFactory(); 353 } 354 355 362 protected boolean setDocumentContent(IDocument document, IEditorInput editorInput) throws CoreException { 363 if (editorInput instanceof IFileEditorInput) { 364 IFile file= ((IFileEditorInput) editorInput).getFile(); 365 InputStream stream= file.getContents(false); 366 try { 367 setDocumentContent(document, stream); 368 } finally { 369 try { 370 stream.close(); 371 } catch (IOException x) { 372 } 373 } 374 return true; 375 } 376 return super.setDocumentContent(document, editorInput); 377 } 378 379 383 protected boolean setDocumentContent(IDocument document, IEditorInput editorInput, String encoding) throws CoreException { 384 if (editorInput instanceof IFileEditorInput) { 385 IFile file= ((IFileEditorInput) editorInput).getFile(); 386 InputStream contentStream= file.getContents(false); 387 try { 388 389 FileInfo info= (FileInfo)getElementInfo(editorInput); 390 boolean removeBOM= false; 391 if (CHARSET_UTF_8.equals(encoding)) { 392 if (info != null) 393 removeBOM= info.fHasBOM; 394 else 395 removeBOM= hasBOM(editorInput); 396 } 397 398 403 if (removeBOM) { 404 int n= 0; 405 do { 406 int bytes= contentStream.read(new byte[IContentDescription.BOM_UTF_8.length]); 407 if (bytes == -1) 408 throw new IOException (); 409 n += bytes; 410 } while (n < IContentDescription.BOM_UTF_8.length); 411 } 412 413 setDocumentContent(document, contentStream, encoding); 414 415 } catch (IOException ex) { 416 String message= (ex.getMessage() != null ? ex.getMessage() : ""); IStatus s= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.OK, message, ex); 418 throw new CoreException(s); 419 } finally { 420 try { 421 contentStream.close(); 422 } catch (IOException e1) { 423 } 424 } 425 return true; 426 } 427 return super.setDocumentContent(document, editorInput, encoding); 428 } 429 430 433 protected IAnnotationModel createAnnotationModel(Object element) throws CoreException { 434 if (element instanceof IFileEditorInput) { 435 IFileEditorInput input= (IFileEditorInput) element; 436 return new ResourceMarkerAnnotationModel(input.getFile()); 437 } 438 439 return super.createAnnotationModel(element); 440 } 441 442 452 protected void checkSynchronizationState(long cachedModificationStamp, IResource resource) throws CoreException { 453 if (cachedModificationStamp != computeModificationStamp(resource)) { 454 Status status= new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IResourceStatus.OUT_OF_SYNC_LOCAL, TextEditorMessages.FileDocumentProvider_error_out_of_sync, null); 455 throw new CoreException(status); 456 } 457 } 458 459 465 protected long computeModificationStamp(IResource resource) { 466 long modificationStamp= resource.getModificationStamp(); 467 468 IPath path= resource.getLocation(); 469 if (path == null) 470 return modificationStamp; 471 472 modificationStamp= path.toFile().lastModified(); 473 return modificationStamp; 474 } 475 476 479 public long getModificationStamp(Object element) { 480 481 if (element instanceof IFileEditorInput) { 482 IFileEditorInput input= (IFileEditorInput) element; 483 return computeModificationStamp(input.getFile()); 484 } 485 486 return super.getModificationStamp(element); 487 } 488 489 492 public long getSynchronizationStamp(Object element) { 493 494 if (element instanceof IFileEditorInput) { 495 FileInfo info= (FileInfo) getElementInfo(element); 496 if (info != null) 497 return info.fModificationStamp; 498 } 499 500 return super.getSynchronizationStamp(element); 501 } 502 503 507 protected void doSynchronize(Object element, IProgressMonitor monitor) throws CoreException { 508 if (element instanceof IFileEditorInput) { 509 510 IFileEditorInput input= (IFileEditorInput) element; 511 512 FileInfo info= (FileInfo) getElementInfo(element); 513 if (info != null) { 514 515 if (info.fFileSynchronizer != null) { 516 info.fFileSynchronizer.uninstall(); 517 refreshFile(input.getFile(), monitor); 518 info.fFileSynchronizer.install(); 519 } else { 520 refreshFile(input.getFile(), monitor); 521 } 522 523 handleElementContentChanged((IFileEditorInput) element); 524 } 525 return; 526 527 } 528 super.doSynchronize(element, monitor); 529 } 530 531 534 public boolean isDeleted(Object element) { 535 536 if (element instanceof IFileEditorInput) { 537 IFileEditorInput input= (IFileEditorInput) element; 538 539 IPath path= input.getFile().getLocation(); 540 if (path == null) 541 return true; 542 543 return !path.toFile().exists(); 544 } 545 546 return super.isDeleted(element); 547 } 548 549 552 protected void doSaveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) throws CoreException { 553 if (element instanceof IFileEditorInput) { 554 555 IFileEditorInput input= (IFileEditorInput) element; 556 String encoding= null; 557 558 FileInfo info= (FileInfo) getElementInfo(element); 559 IFile file= input.getFile(); 560 encoding= getCharsetForNewFile(file, document, info); 561 562 Charset charset; 563 try { 564 charset= Charset.forName(encoding); 565 } catch (UnsupportedCharsetException ex) { 566 String message= NLSUtility.format(TextEditorMessages.DocumentProvider_error_unsupported_encoding_message_arg, encoding); 567 IStatus s= new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, message, ex); 568 throw new CoreException(s); 569 } catch (IllegalCharsetNameException ex) { 570 String message= NLSUtility.format(TextEditorMessages.DocumentProvider_error_illegal_encoding_message_arg, encoding); 571 IStatus s= new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, IStatus.OK, message, ex); 572 throw new CoreException(s); 573 } 574 575 CharsetEncoder encoder= charset.newEncoder(); 576 encoder.onMalformedInput(CodingErrorAction.REPLACE); 577 encoder.onUnmappableCharacter(CodingErrorAction.REPORT); 578 579 InputStream stream; 580 581 try { 582 byte[] bytes; 583 ByteBuffer byteBuffer= encoder.encode(CharBuffer.wrap(document.get())); 584 if (byteBuffer.hasArray()) 585 bytes= byteBuffer.array(); 586 else { 587 bytes= new byte[byteBuffer.limit()]; 588 byteBuffer.get(bytes); 589 } 590 stream= new ByteArrayInputStream (bytes, 0, byteBuffer.limit()); 591 } catch (CharacterCodingException ex) { 592 Assert.isTrue(ex instanceof UnmappableCharacterException ); 593 String message= NLSUtility.format(TextEditorMessages.DocumentProvider_error_charset_mapping_failed_message_arg, encoding); 594 IStatus s= new Status(IStatus.ERROR, EditorsUI.PLUGIN_ID, EditorsUI.CHARSET_MAPPING_FAILED, message, null); 595 throw new CoreException(s); 596 } 597 598 603 if (info != null && info.fHasBOM && CHARSET_UTF_8.equals(encoding)) 604 stream= new SequenceInputStream (new ByteArrayInputStream (IContentDescription.BOM_UTF_8), stream); 605 606 if (file.exists()) { 607 608 if (info != null && !overwrite) 609 checkSynchronizationState(info.fModificationStamp, file); 610 611 fireElementStateChanging(element); 613 try { 614 file.setContents(stream, overwrite, true, monitor); 615 } catch (CoreException x) { 616 fireElementStateChangeFailed(element); 618 throw x; 619 } catch (RuntimeException x) { 620 fireElementStateChangeFailed(element); 622 throw x; 623 } 624 625 628 if (info != null) { 629 630 ResourceMarkerAnnotationModel model= (ResourceMarkerAnnotationModel) info.fModel; 631 if (model != null) 632 model.updateMarkers(info.fDocument); 633 634 info.fModificationStamp= computeModificationStamp(file); 635 } 636 637 } else { 638 try { 639 monitor.beginTask(TextEditorMessages.FileDocumentProvider_task_saving, 2000); 640 ContainerCreator creator = new ContainerCreator(file.getWorkspace(), file.getParent().getFullPath()); 641 creator.createContainer(new SubProgressMonitor(monitor, 1000)); 642 file.create(stream, false, new SubProgressMonitor(monitor, 1000)); 643 } 644 finally { 645 monitor.done(); 646 } 647 } 648 649 } else { 650 super.doSaveDocument(monitor, element, document, overwrite); 651 } 652 } 653 654 657 private String getCharsetForNewFile(IFile targetFile, IDocument document, FileInfo info) { 658 String encoding; 660 try { 661 encoding= targetFile.getCharset(false); 662 } catch (CoreException ex) { 663 encoding= null; 664 } 665 if (encoding != null) 666 return encoding; 667 668 Reader reader= new DocumentReader(document); 670 try { 671 QualifiedName[] options= new QualifiedName[] { IContentDescription.CHARSET, IContentDescription.BYTE_ORDER_MARK }; 672 IContentDescription description= Platform.getContentTypeManager().getDescriptionFor(reader, targetFile.getName(), options); 673 if (description != null) { 674 encoding= description.getCharset(); 675 if (encoding != null) 676 return encoding; 677 } 678 } catch (IOException ex) { 679 } finally { 681 try { 682 reader.close(); 683 } catch (IOException x) { 684 } 685 } 686 687 if (info != null && info.fHasBOM) 689 return info.fEncoding; 690 691 try { 693 return targetFile.getParent().getDefaultCharset(); 694 } catch (CoreException ex) { 695 return ResourcesPlugin.getEncoding(); 697 } 698 } 699 700 703 protected ElementInfo createElementInfo(Object element) throws CoreException { 704 if (element instanceof IFileEditorInput) { 705 706 IFileEditorInput input= (IFileEditorInput) element; 707 708 try { 709 refreshFile(input.getFile()); 710 } catch (CoreException x) { 711 handleCoreException(x, TextEditorMessages.FileDocumentProvider_createElementInfo); 712 } 713 714 IDocument d= null; 715 IStatus s= null; 716 717 try { 718 d= createDocument(element); 719 } catch (CoreException x) { 720 handleCoreException(x, TextEditorMessages.FileDocumentProvider_createElementInfo); 721 s= x.getStatus(); 722 d= createEmptyDocument(); 723 } 724 725 if (d instanceof IDocumentExtension4) { 727 String initalLineDelimiter= getLineDelimiterPreference(input.getFile()); 728 if (initalLineDelimiter != null) 729 ((IDocumentExtension4)d).setInitialLineDelimiter(initalLineDelimiter); 730 } 731 732 IAnnotationModel m= createAnnotationModel(element); 733 FileSynchronizer f= new FileSynchronizer(input); 734 f.install(); 735 736 FileInfo info= new FileInfo(d, m, f); 737 info.fModificationStamp= computeModificationStamp(input.getFile()); 738 info.fStatus= s; 739 info.fEncoding= getPersistedEncoding(element); 740 &nb
|