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