new version with WorkingCopy Management
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / 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.phpeditor;
13
14
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18
19 import net.sourceforge.phpdt.core.BufferChangedEvent;
20 import net.sourceforge.phpdt.core.IBuffer;
21 import net.sourceforge.phpdt.core.IBufferChangedListener;
22 import net.sourceforge.phpdt.core.IOpenable;
23 import net.sourceforge.phpdt.core.JavaModelException;
24
25 import org.eclipse.core.resources.IResource;
26 import org.eclipse.core.runtime.CoreException;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.IStatus;
29 import org.eclipse.jface.text.Assert;
30 import org.eclipse.jface.text.BadLocationException;
31 import org.eclipse.jface.text.DocumentEvent;
32 import org.eclipse.jface.text.IDocument;
33 import org.eclipse.jface.text.IDocumentListener;
34 import org.eclipse.jface.text.ILineTracker;
35 import org.eclipse.jface.text.IRegion;
36 import org.eclipse.swt.widgets.Display;
37
38
39
40 /**
41  * Adapts <code>IDocument</code> to <code>IBuffer</code>. Uses the
42  * same algorithm as the text widget to determine the buffer's line delimiter. 
43  * All text inserted into the buffer is converted to this line delimiter.
44  * This class is <code>public</code> for test purposes only.
45  */
46 public class DocumentAdapter implements IBuffer, IDocumentListener {
47         
48                 /**
49                  * Internal implementation of a NULL instanceof IBuffer.
50                  */
51                 static private class NullBuffer implements IBuffer {
52                         
53                         public void addBufferChangedListener(IBufferChangedListener listener) {}
54                 
55                         public void append(char[] text) {}
56                 
57                         public void append(String text) {}
58                 
59                         public void close() {}
60                 
61                         public char getChar(int position) {
62                                 return 0;
63                         }
64                 
65                         public char[] getCharacters() {
66                                 return null;
67                         }
68                 
69                         public String getContents() {
70                                 return null;
71                         }
72                 
73                         public int getLength() {
74                                 return 0;
75                         }
76                 
77                         public IOpenable getOwner() {
78                                 return null;
79                         }
80                 
81                         public String getText(int offset, int length) {
82                                 return null;
83                         }
84                 
85                         public IResource getUnderlyingResource() {
86                                 return null;
87                         }
88                 
89                         public boolean hasUnsavedChanges() {
90                                 return false;
91                         }
92                 
93                         public boolean isClosed() {
94                                 return false;
95                         }
96                 
97                         public boolean isReadOnly() {
98                                 return true;
99                         }
100                 
101                         public void removeBufferChangedListener(IBufferChangedListener listener) {}
102                 
103                         public void replace(int position, int length, char[] text) {}
104                 
105                         public void replace(int position, int length, String text) {}
106                 
107                         public void save(IProgressMonitor progress, boolean force) throws JavaModelException {}
108                 
109                         public void setContents(char[] contents) {}
110                 
111                         public void setContents(String contents) {}
112                 };
113                 
114         
115         /** NULL implementing <code>IBuffer</code> */
116         public final static IBuffer NULL= new NullBuffer();
117                 
118         
119         /**
120          *  Executes a document set content call in the ui thread.
121          */
122         protected class DocumentSetCommand implements Runnable {
123                 
124                 private String fContents;
125                 
126                 public void run() {
127                         fDocument.set(fContents);
128                 }
129         
130                 public void set(String contents) {
131                         fContents= contents;
132                         Display.getDefault().syncExec(this);
133                 }
134         };
135         
136         /**
137          * Executes a document replace call in the ui thread.
138          */
139         protected class DocumentReplaceCommand implements Runnable {
140                 
141                 private int fOffset;
142                 private int fLength;
143                 private String fText;
144                 
145                 public void run() {
146                         try {
147                                 fDocument.replace(fOffset, fLength, fText);
148                         } catch (BadLocationException x) {
149                                 // ignore
150                         }
151                 }
152                 
153                 public void replace(int offset, int length, String text) {
154                         fOffset= offset;
155                         fLength= length;
156                         fText= text;
157                         Display.getDefault().syncExec(this);
158                 }
159         };
160         
161         private IOpenable fOwner;
162         private IDocument fDocument;
163         private DocumentSetCommand fSetCmd= new DocumentSetCommand();
164         private DocumentReplaceCommand fReplaceCmd= new DocumentReplaceCommand();
165         
166         private Object fProviderKey;
167         private PHPDocumentProvider fProvider;
168         private String fLineDelimiter;
169         private ILineTracker fLineTracker;
170         
171         private List fBufferListeners= new ArrayList(3);
172         
173         private IStatus fStatus;
174         
175         /**
176          * This method is <code>public</code> for test purposes only.
177          */
178         public DocumentAdapter(IOpenable owner, IDocument document, ILineTracker lineTracker, PHPDocumentProvider provider, Object providerKey) {
179                 
180                 Assert.isNotNull(document);
181                 Assert.isNotNull(lineTracker);
182                 
183                 fOwner= owner;
184                 fDocument= document;
185                 fLineTracker= lineTracker;
186                 fProvider= provider;
187                 fProviderKey= providerKey;
188                 
189                 fDocument.addPrenotifiedDocumentListener(this);
190         }
191         
192         /**
193          * Sets the status of this document adapter.
194          */
195         public void setStatus(IStatus status) {
196                 fStatus= status;
197         }
198         
199         /**
200          * Returns the status of this document adapter.
201          */
202         public IStatus getStatus() {
203                 return fStatus;
204         }
205         
206         /**
207          * Returns the adapted document.
208          * 
209          * @return the adapted document
210          */
211         public IDocument getDocument() {
212                 return fDocument;
213         }
214         
215         /**
216          * Returns the line delimiter of this buffer. As a document has a set of
217          * valid line delimiters, this set must be reduced to size 1.
218          */
219         protected String getLineDelimiter() {
220                 
221                 if (fLineDelimiter == null) {
222                         
223                         try {
224                                 fLineDelimiter= fDocument.getLineDelimiter(0);
225                         } catch (BadLocationException x) {
226                         }
227                         
228                         if (fLineDelimiter == null) {
229                                 /*
230                                  * Follow up fix for: 1GF5UU0: ITPJUI:WIN2000 - "Organize Imports" in java editor inserts lines in wrong format
231                                  * The line delimiter must always be a legal document line delimiter.
232                                  */
233                                 String sysLineDelimiter= System.getProperty("line.separator"); //$NON-NLS-1$
234                                 String[] delimiters= fDocument.getLegalLineDelimiters();
235                                 Assert.isTrue(delimiters.length > 0);
236                                 for (int i= 0; i < delimiters.length; i++) {
237                                         if (delimiters[i].equals(sysLineDelimiter)) {
238                                                 fLineDelimiter= sysLineDelimiter;
239                                                 break;
240                                         }
241                                 }
242                                 
243                                 if (fLineDelimiter == null) {
244                                         // system line delimiter is not a legal document line delimiter
245                                         fLineDelimiter= delimiters[0];
246                                 }
247                         }
248                 }
249                 
250                 return fLineDelimiter;
251         }       
252         
253         /**
254          * Converts the given string to the line delimiter of this buffer.
255          * This method is <code>public</code> for test purposes only.
256          */
257         public String normalize(String text) {
258                 fLineTracker.set(text);
259                 
260                 int lines= fLineTracker.getNumberOfLines();
261                 if (lines <= 1)
262                         return text;
263                         
264                 StringBuffer buffer= new StringBuffer(text);
265                 
266                 try {
267                         IRegion previous= fLineTracker.getLineInformation(0);
268                         for (int i= 1; i < lines; i++) {
269                                 int lastLineEnd= previous.getOffset() + previous.getLength();
270                                 int lineStart= fLineTracker.getLineInformation(i).getOffset();
271                                 fLineTracker.replace(lastLineEnd,  lineStart - lastLineEnd, getLineDelimiter());
272                                 buffer.replace(lastLineEnd, lineStart, getLineDelimiter());
273                                 previous= fLineTracker.getLineInformation(i);
274                         }
275                         
276                         // last line
277                         String delimiter= fLineTracker.getLineDelimiter(lines -1);
278                         if (delimiter != null && delimiter.length() > 0)
279                                 buffer.replace(previous.getOffset() + previous.getLength(), buffer.length(), getLineDelimiter());
280                                 
281                         return buffer.toString();
282                 } catch (BadLocationException x) {
283                 }
284                 
285                 return text;
286         }
287         
288         /*
289          * @see IBuffer#addBufferChangedListener(IBufferChangedListener)
290          */
291         public void addBufferChangedListener(IBufferChangedListener listener) {
292                 Assert.isNotNull(listener);
293                 if (!fBufferListeners.contains(listener))
294                         fBufferListeners.add(listener);
295         }
296         
297         /*
298          * @see IBuffer#removeBufferChangedListener(IBufferChangedListener)
299          */
300         public void removeBufferChangedListener(IBufferChangedListener listener) {
301                 Assert.isNotNull(listener);
302                 fBufferListeners.remove(listener);
303         }
304         
305         /*
306          * @see IBuffer#append(char[])
307          */
308         public void append(char[] text) {
309                 append(new String(text));
310         }
311         
312         /*
313          * @see IBuffer#append(String) 
314          */
315         public void append(String text) {
316                 fReplaceCmd.replace(fDocument.getLength(), 0, normalize(text));
317         }
318         
319         /*
320          * @see IBuffer#close()
321          */
322         public void close() {
323                 
324                 if (isClosed())
325                         return;
326                         
327                 IDocument d= fDocument;
328                 fDocument= null;
329                 d.removePrenotifiedDocumentListener(this);
330                 
331                 fireBufferChanged(new BufferChangedEvent(this, 0, 0, null));
332                 fBufferListeners.clear();
333         }
334         
335         /*
336          * @see IBuffer#getChar(int)
337          */
338         public char getChar(int position) {
339                 try {
340                         return fDocument.getChar(position);
341                 } catch (BadLocationException x) {
342                         throw new ArrayIndexOutOfBoundsException();
343                 }
344         }
345         
346         /*
347          *  @see IBuffer#getCharacters()
348          */
349         public char[] getCharacters() {
350                 String content= getContents();
351                 return content == null ? null : content.toCharArray();
352         }
353         
354         /*
355          * @see IBuffer#getContents()
356          */
357         public String getContents() {
358                 return fDocument.get();
359         }
360         
361         /*
362          * @see IBuffer#getLength()
363          */
364         public int getLength() {
365                 return fDocument.getLength();
366         }
367         
368         /*
369          * @see IBuffer#getOwner()
370          */
371         public IOpenable getOwner() {
372                 return (IOpenable) fOwner;
373         }
374         
375         /*
376          * @see IBuffer#getText(int, int)
377          */
378         public String getText(int offset, int length) {
379                 try {
380                         return fDocument.get(offset, length);
381                 } catch (BadLocationException x) {
382                         throw new ArrayIndexOutOfBoundsException();
383                 }
384         }
385         
386         /*
387          * @see IBuffer#getUnderlyingResource()
388          */
389         public IResource getUnderlyingResource() {
390                 return fProvider != null ? fProvider.getUnderlyingResource(fProviderKey) : null;
391         }
392         
393         /*
394          * @see IBuffer#hasUnsavedChanges()
395          */
396         public boolean hasUnsavedChanges() {
397                 return fProvider != null ? fProvider.canSaveDocument(fProviderKey) : false;
398         }
399         
400         /*
401          * @see IBuffer#isClosed()
402          */
403         public boolean isClosed() {
404                 return fDocument == null;
405         }
406         
407         /*
408          * @see IBuffer#isReadOnly()
409          */
410         public boolean isReadOnly() {
411                 IResource resource= getUnderlyingResource();
412                 return resource == null ? true : resource.isReadOnly();
413         }
414         
415         /*
416          * @see IBuffer#replace(int, int, char[])
417          */
418         public void replace(int position, int length, char[] text) {
419                 replace(position, length, new String(text));
420         }
421         
422         /*
423          * @see IBuffer#replace(int, int, String)
424          */
425         public void replace(int position, int length, String text) {
426                 fReplaceCmd.replace(position, length, normalize(text));
427         }
428         
429         /*
430          * @see IBuffer#save(IProgressMonitor, boolean)
431          */
432         public void save(IProgressMonitor progress, boolean force) throws JavaModelException {
433                 if (fProvider != null) {
434                         try {
435                                 fProvider.saveDocumentContent(progress, fProviderKey, fDocument, force);
436                         } catch (CoreException e) {
437                                 throw new JavaModelException(e);
438                         }
439                 }
440         }
441         
442         /*
443          * @see IBuffer#setContents(char[])
444          */
445         public void setContents(char[] contents) {
446                 setContents(new String(contents));
447         }
448         
449         /*
450          * @see IBuffer#setContents(String)
451          */
452         public void setContents(String contents) {
453                 int oldLength= fDocument.getLength();
454                 
455                 if (contents == null) {
456                         
457                         if (oldLength != 0)
458                                 fSetCmd.set(""); //$NON-NLS-1$
459                 
460                 } else {
461                         
462                         // set only if different
463                         String newContents= normalize(contents);
464                         int newLength= newContents.length();
465                         
466                         if (oldLength != newLength || !newContents.equals(fDocument.get()))
467                                 fSetCmd.set(newContents);
468                 }
469         }
470         
471         /*
472          * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent)
473          */
474         public void documentAboutToBeChanged(DocumentEvent event) {
475                 // there is nothing to do here
476         }
477
478         /*
479          * @see IDocumentListener#documentChanged(DocumentEvent)
480          */
481         public void documentChanged(DocumentEvent event) {
482                 fireBufferChanged(new BufferChangedEvent(this, event.getOffset(), event.getLength(), event.getText()));
483         }
484         
485         private void fireBufferChanged(BufferChangedEvent event) {
486                 if (fBufferListeners != null && fBufferListeners.size() > 0) {
487                         Iterator e= new ArrayList(fBufferListeners).iterator();
488                         while (e.hasNext())
489                                 ((IBufferChangedListener) e.next()).bufferChanged(event);
490                 }
491         }
492 }