1) Fixed issue #705: Take the global 'Undo history size' to overwrite the default...
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / DocumentAdapter.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2003 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11
12 package net.sourceforge.phpeclipse;
13
14 import java.util.ArrayList;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Set;
19
20 import net.sourceforge.phpdt.core.BufferChangedEvent;
21 import net.sourceforge.phpdt.core.IBuffer;
22 import net.sourceforge.phpdt.core.IBufferChangedListener;
23 import net.sourceforge.phpdt.core.IOpenable;
24 import net.sourceforge.phpdt.core.JavaModelException;
25 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
26
27 import org.eclipse.core.filebuffers.FileBuffers;
28 import org.eclipse.core.filebuffers.ITextFileBuffer;
29 import org.eclipse.core.filebuffers.ITextFileBufferManager;
30 import org.eclipse.core.resources.IFile;
31 import org.eclipse.core.resources.IResource;
32 import org.eclipse.core.runtime.CoreException;
33 import org.eclipse.core.runtime.IPath;
34 import org.eclipse.core.runtime.IProgressMonitor;
35 import org.eclipse.core.runtime.IStatus;
36 import org.eclipse.core.runtime.NullProgressMonitor;
37 //incastrix
38 //import org.eclipse.jface.text.Assert;
39 import org.eclipse.core.runtime.Assert;
40 import org.eclipse.jface.text.BadLocationException;
41 import org.eclipse.jface.text.DefaultLineTracker;
42 import org.eclipse.jface.text.DocumentEvent;
43 import org.eclipse.jface.text.IDocument;
44 import org.eclipse.jface.text.IDocumentListener;
45 import org.eclipse.swt.widgets.Display;
46
47 /**
48  * Adapts <code>IDocument</code> to <code>IBuffer</code>. Uses the same
49  * algorithm as the text widget to determine the buffer's line delimiter. All
50  * text inserted into the buffer is converted to this line delimiter. This class
51  * is <code>public</code> for test purposes only.
52  */
53 public class DocumentAdapter implements IBuffer, IDocumentListener {
54
55         /**
56          * Internal implementation of a NULL instanceof IBuffer.
57          */
58         static private class NullBuffer implements IBuffer {
59                 public void addBufferChangedListener(IBufferChangedListener listener) {
60                 }
61
62                 public void append(char[] text) {
63                 }
64
65                 public void append(String text) {
66                 }
67
68                 public void close() {
69                 }
70
71                 public char getChar(int position) {
72                         return 0;
73                 }
74
75                 public char[] getCharacters() {
76                         return null;
77                 }
78
79                 public String getContents() {
80                         return null;
81                 }
82
83                 public int getLength() {
84                         return 0;
85                 }
86
87                 public IOpenable getOwner() {
88                         return null;
89                 }
90
91                 public String getText(int offset, int length) {
92                         return null;
93                 }
94
95                 public IResource getUnderlyingResource() {
96                         return null;
97                 }
98
99                 public boolean hasUnsavedChanges() {
100                         return false;
101                 }
102
103                 public boolean isClosed() {
104                         return false;
105                 }
106
107                 public boolean isReadOnly() {
108                         return true;
109                 }
110
111                 public void removeBufferChangedListener(IBufferChangedListener listener) {
112                 }
113
114                 public void replace(int position, int length, char[] text) {
115                 }
116
117                 public void replace(int position, int length, String text) {
118                 }
119
120                 public void save(IProgressMonitor progress, boolean force)
121                                 throws JavaModelException {
122                 }
123
124                 public void setContents(char[] contents) {
125                 }
126
127                 public void setContents(String contents) {
128                 }
129         }
130
131         /** NULL implementing <code>IBuffer</code> */
132         public final static IBuffer NULL = new NullBuffer();
133
134         /**
135          * Executes a document set content call in the ui thread.
136          */
137         protected class DocumentSetCommand implements Runnable {
138
139                 private String fContents;
140
141                 public void run() {
142                         fDocument.set(fContents);
143                 }
144
145                 public void set(String contents) {
146                         fContents = contents;
147                         Display.getDefault().syncExec(this);
148                 }
149         }
150
151         /**
152          * Executes a document replace call in the ui thread.
153          */
154         protected class DocumentReplaceCommand implements Runnable {
155
156                 private int fOffset;
157
158                 private int fLength;
159
160                 private String fText;
161
162                 public void run() {
163                         try {
164                                 fDocument.replace(fOffset, fLength, fText);
165                         } catch (BadLocationException x) {
166                                 // ignore
167                         }
168                 }
169
170                 public void replace(int offset, int length, String text) {
171                         fOffset = offset;
172                         fLength = length;
173                         fText = text;
174                         Display.getDefault().syncExec(this);
175                 }
176         }
177
178         private static final boolean DEBUG_LINE_DELIMITERS = true;
179
180         private IOpenable fOwner;
181
182         private IFile fFile;
183
184         private ITextFileBuffer fTextFileBuffer;
185
186         private IDocument fDocument;
187
188         private DocumentSetCommand fSetCmd = new DocumentSetCommand();
189
190         private DocumentReplaceCommand fReplaceCmd = new DocumentReplaceCommand();
191
192         private Set fLegalLineDelimiters;
193
194         private List fBufferListeners = new ArrayList(3);
195
196         private IStatus fStatus;
197
198         /**
199          * This method is <code>public</code> for test purposes only.
200          */
201         public DocumentAdapter(IOpenable owner, IFile file) {
202
203                 fOwner = owner;
204                 fFile = file;
205
206                 initialize();
207         }
208
209         private void initialize() {
210                 ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
211                 IPath location = fFile.getFullPath();
212                 try {
213                         manager.connect(location, new NullProgressMonitor());
214                         fTextFileBuffer = manager.getTextFileBuffer(location);
215                         fDocument = fTextFileBuffer.getDocument();
216                 } catch (CoreException x) {
217                         fStatus = x.getStatus();
218                         fDocument = manager.createEmptyDocument(location);
219                 }
220                 fDocument.addPrenotifiedDocumentListener(this);
221         }
222
223         /**
224          * Returns the status of this document adapter.
225          */
226         public IStatus getStatus() {
227                 if (fStatus != null)
228                         return fStatus;
229                 if (fTextFileBuffer != null)
230                         return fTextFileBuffer.getStatus();
231                 return null;
232         }
233
234         /**
235          * Returns the adapted document.
236          * 
237          * @return the adapted document
238          */
239         public IDocument getDocument() {
240                 return fDocument;
241         }
242
243         /*
244          * @see IBuffer#addBufferChangedListener(IBufferChangedListener)
245          */
246         public void addBufferChangedListener(IBufferChangedListener listener) {
247                 Assert.isNotNull(listener);
248                 if (!fBufferListeners.contains(listener))
249                         fBufferListeners.add(listener);
250         }
251
252         /*
253          * @see IBuffer#removeBufferChangedListener(IBufferChangedListener)
254          */
255         public void removeBufferChangedListener(IBufferChangedListener listener) {
256                 Assert.isNotNull(listener);
257                 fBufferListeners.remove(listener);
258         }
259
260         /*
261          * @see IBuffer#append(char[])
262          */
263         public void append(char[] text) {
264                 append(new String(text));
265         }
266
267         /*
268          * @see IBuffer#append(String)
269          */
270         public void append(String text) {
271                 if (DEBUG_LINE_DELIMITERS) {
272                         validateLineDelimiters(text);
273                 }
274                 fReplaceCmd.replace(fDocument.getLength(), 0, text);
275         }
276
277         /*
278          * @see IBuffer#close()
279          */
280         public void close() {
281
282                 if (isClosed())
283                         return;
284
285                 IDocument d = fDocument;
286                 fDocument = null;
287                 d.removePrenotifiedDocumentListener(this);
288
289                 if (fTextFileBuffer != null) {
290                         ITextFileBufferManager manager = FileBuffers
291                                         .getTextFileBufferManager();
292                         try {
293                                 manager.disconnect(fTextFileBuffer.getLocation(),
294                                                 new NullProgressMonitor());
295                         } catch (CoreException x) {
296                                 // ignore
297                         }
298                         fTextFileBuffer = null;
299                 }
300
301                 fireBufferChanged(new BufferChangedEvent(this, 0, 0, null));
302                 fBufferListeners.clear();
303         }
304
305         /*
306          * @see IBuffer#getChar(int)
307          */
308         public char getChar(int position) {
309                 try {
310                         return fDocument.getChar(position);
311                 } catch (BadLocationException x) {
312                         throw new ArrayIndexOutOfBoundsException();
313                 }
314         }
315
316         /*
317          * @see IBuffer#getCharacters()
318          */
319         public char[] getCharacters() {
320                 String content = getContents();
321                 return content == null ? null : content.toCharArray();
322         }
323
324         /*
325          * @see IBuffer#getContents()
326          */
327         public String getContents() {
328                 return fDocument.get();
329         }
330
331         /*
332          * @see IBuffer#getLength()
333          */
334         public int getLength() {
335                 return fDocument.getLength();
336         }
337
338         /*
339          * @see IBuffer#getOwner()
340          */
341         public IOpenable getOwner() {
342                 return fOwner;
343         }
344
345         /*
346          * @see IBuffer#getText(int, int)
347          */
348         public String getText(int offset, int length) {
349                 try {
350                         return fDocument.get(offset, length);
351                 } catch (BadLocationException x) {
352                         throw new ArrayIndexOutOfBoundsException();
353                 }
354         }
355
356         /*
357          * @see IBuffer#getUnderlyingResource()
358          */
359         public IResource getUnderlyingResource() {
360                 return fFile;
361         }
362
363         /*
364          * @see IBuffer#hasUnsavedChanges()
365          */
366         public boolean hasUnsavedChanges() {
367                 return fTextFileBuffer != null ? fTextFileBuffer.isDirty() : false;
368         }
369
370         /*
371          * @see IBuffer#isClosed()
372          */
373         public boolean isClosed() {
374                 return fDocument == null;
375         }
376
377         /*
378          * @see IBuffer#isReadOnly()
379          */
380         public boolean isReadOnly() {
381                 IResource resource = getUnderlyingResource();
382                 return resource == null ? true : resource.getResourceAttributes()
383                                 .isReadOnly();
384         }
385
386         /*
387          * @see IBuffer#replace(int, int, char[])
388          */
389         public void replace(int position, int length, char[] text) {
390                 replace(position, length, new String(text));
391         }
392
393         /*
394          * @see IBuffer#replace(int, int, String)
395          */
396         public void replace(int position, int length, String text) {
397                 if (DEBUG_LINE_DELIMITERS) {
398                         validateLineDelimiters(text);
399                 }
400                 fReplaceCmd.replace(position, length, text);
401         }
402
403         /*
404          * @see IBuffer#save(IProgressMonitor, boolean)
405          */
406         public void save(IProgressMonitor progress, boolean force)
407                         throws JavaModelException {
408                 try {
409                         if (fTextFileBuffer != null)
410                                 fTextFileBuffer.commit(progress, force);
411                 } catch (CoreException e) {
412                         throw new JavaModelException(e);
413                 }
414         }
415
416         /*
417          * @see IBuffer#setContents(char[])
418          */
419         public void setContents(char[] contents) {
420                 setContents(new String(contents));
421         }
422
423         /*
424          * @see IBuffer#setContents(String)
425          */
426         public void setContents(String contents) {
427                 int oldLength = fDocument.getLength();
428
429                 if (contents == null) {
430
431                         if (oldLength != 0)
432                                 fSetCmd.set(""); //$NON-NLS-1$
433
434                 } else {
435
436                         // set only if different
437                         if (DEBUG_LINE_DELIMITERS) {
438                                 validateLineDelimiters(contents);
439                         }
440
441                         if (!contents.equals(fDocument.get()))
442                                 fSetCmd.set(contents);
443                 }
444         }
445
446         private void validateLineDelimiters(String contents) {
447
448                 if (fLegalLineDelimiters == null) {
449                         // collect all line delimiters in the document
450                         HashSet existingDelimiters = new HashSet();
451
452                         for (int i = fDocument.getNumberOfLines() - 1; i >= 0; i--) {
453                                 try {
454                                         String curr = fDocument.getLineDelimiter(i);
455                                         if (curr != null) {
456                                                 existingDelimiters.add(curr);
457                                         }
458                                 } catch (BadLocationException e) {
459                                         PHPeclipsePlugin.log(e);
460                                 }
461                         }
462                         if (existingDelimiters.isEmpty()) {
463                                 return; // first insertion of a line delimiter: no test
464                         }
465                         fLegalLineDelimiters = existingDelimiters;
466
467                 }
468
469                 DefaultLineTracker tracker = new DefaultLineTracker();
470                 tracker.set(contents);
471
472                 int lines = tracker.getNumberOfLines();
473                 if (lines <= 1)
474                         return;
475
476                 for (int i = 0; i < lines; i++) {
477                         try {
478                                 String curr = tracker.getLineDelimiter(i);
479                                 if (curr != null && !fLegalLineDelimiters.contains(curr)) {
480                                         StringBuffer buf = new StringBuffer(
481                                                         "New line delimiter added to new code: "); //$NON-NLS-1$
482                                         for (int k = 0; k < curr.length(); k++) {
483                                                 buf.append(String.valueOf((int) curr.charAt(k)));
484                                         }
485                                         PHPeclipsePlugin.log(new Exception(buf.toString()));
486                                 }
487                         } catch (BadLocationException e) {
488                                 PHPeclipsePlugin.log(e);
489                         }
490                 }
491         }
492
493         /*
494          * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
495          */
496         public void documentAboutToBeChanged(DocumentEvent event) {
497                 // there is nothing to do here
498         }
499
500         /*
501          * @see IDocumentListener#documentChanged(DocumentEvent)
502          */
503         public void documentChanged(DocumentEvent event) {
504                 fireBufferChanged(new BufferChangedEvent(this, event.getOffset(), event
505                                 .getLength(), event.getText()));
506         }
507
508         private void fireBufferChanged(BufferChangedEvent event) {
509                 if (fBufferListeners != null && fBufferListeners.size() > 0) {
510                         Iterator e = new ArrayList(fBufferListeners).iterator();
511                         while (e.hasNext())
512                                 ((IBufferChangedListener) e.next()).bufferChanged(event);
513                 }
514         }
515 }