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