c26691d29f7a4b3a31d790a58cfd1b3cf026ddae
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / core / Buffer.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 package net.sourceforge.phpdt.internal.core;
12
13 import java.io.ByteArrayInputStream;
14 import java.io.IOException;
15 import java.util.ArrayList;
16
17 import net.sourceforge.phpdt.core.BufferChangedEvent;
18 import net.sourceforge.phpdt.core.IBuffer;
19 import net.sourceforge.phpdt.core.IBufferChangedListener;
20 import net.sourceforge.phpdt.core.IJavaModelStatusConstants;
21 import net.sourceforge.phpdt.core.IOpenable;
22 import net.sourceforge.phpdt.core.JavaModelException;
23
24 import org.eclipse.core.resources.IFile;
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.ISafeRunnable;
29 import org.eclipse.core.runtime.Platform;
30
31 /**
32  * @see IBuffer
33  */
34 public class Buffer implements IBuffer {
35         protected IFile file;
36         protected int flags;
37         protected char[] contents;
38         protected ArrayList changeListeners;
39         protected IOpenable owner;
40         protected int gapStart= -1;
41         protected int gapEnd= -1;
42
43         protected Object lock= new Object();
44
45         protected static final int F_HAS_UNSAVED_CHANGES= 1;
46         protected static final int F_IS_READ_ONLY= 2;
47         protected static final int F_IS_CLOSED= 4;
48
49 /**
50  * Creates a new buffer on an underlying resource.
51  */
52 protected Buffer(IFile file, IOpenable owner, boolean readOnly) {
53         this.file = file;
54         this.owner = owner;
55         if (file == null) {
56                 setReadOnly(readOnly);
57         }
58 }
59 /**
60  * @see IBuffer
61  */
62 public void addBufferChangedListener(IBufferChangedListener listener) {
63         if (this.changeListeners == null) {
64                 this.changeListeners = new ArrayList(5);
65         }
66         if (!this.changeListeners.contains(listener)) {
67                 this.changeListeners.add(listener);
68         }
69 }
70 /**
71  * Append the <code>text</code> to the actual content, the gap is moved
72  * to the end of the <code>text</code>.
73  */
74 public void append(char[] text) {
75         if (!isReadOnly()) {
76                 if (text == null || text.length == 0) {
77                         return;
78                 }
79                 int length = getLength();
80                 moveAndResizeGap(length, text.length);
81                 System.arraycopy(text, 0, this.contents, length, text.length);
82                 this.gapStart += text.length;
83                 this.flags |= F_HAS_UNSAVED_CHANGES;
84                 notifyChanged(new BufferChangedEvent(this, length, 0, new String(text)));
85         }
86 }
87 /**
88  * Append the <code>text</code> to the actual content, the gap is moved
89  * to the end of the <code>text</code>.
90  */
91 public void append(String text) {
92         if (text == null) {
93                 return;
94         }
95         this.append(text.toCharArray());
96 }
97 /**
98  * @see IBuffer
99  */
100 public void close() throws IllegalArgumentException {
101         BufferChangedEvent event = null;
102         synchronized (this.lock) {
103                 if (isClosed())
104                         return;
105                 event = new BufferChangedEvent(this, 0, 0, null);
106                 this.contents = null;
107                 this.flags |= F_IS_CLOSED;
108         }
109         notifyChanged(event); // notify outside of synchronized block
110         this.changeListeners = null;
111 }
112 /**
113  * @see IBuffer
114  */
115 public char getChar(int position) {
116         synchronized (this.lock) {
117                 if (position < this.gapStart) {
118                         return this.contents[position];
119                 }
120                 int gapLength = this.gapEnd - this.gapStart;
121                 return this.contents[position + gapLength];
122         }
123 }
124 /**
125  * @see IBuffer
126  */
127 public char[] getCharacters() {
128         if (this.contents == null) return null;
129         synchronized (this.lock) {
130                 if (this.gapStart < 0) {
131                         return this.contents;
132                 }
133                 int length = this.contents.length;
134                 char[] newContents = new char[length - this.gapEnd + this.gapStart];
135                 System.arraycopy(this.contents, 0, newContents, 0, this.gapStart);
136                 System.arraycopy(this.contents, this.gapEnd, newContents, this.gapStart, length - this.gapEnd);
137                 return newContents;
138         }
139 }
140 /**
141  * @see IBuffer
142  */
143 public String getContents() {
144         char[] chars = this.getCharacters();
145         if (chars == null) return null;
146         return new String(chars);
147 }
148 /**
149  * @see IBuffer
150  */
151 public int getLength() {
152         synchronized (this.lock) {
153                 int length = this.gapEnd - this.gapStart;
154                 return (this.contents.length - length);
155         }
156 }
157 /**
158  * @see IBuffer
159  */
160 public IOpenable getOwner() {
161         return this.owner;
162 }
163 /**
164  * @see IBuffer
165  */
166 public String getText(int offset, int length) {
167         if (this.contents == null)
168                 return ""; //$NON-NLS-1$
169         synchronized (this.lock) {
170                 if (offset + length < this.gapStart)
171                         return new String(this.contents, offset, length);
172                 if (this.gapStart < offset) {
173                         int gapLength = this.gapEnd - this.gapStart;
174                         return new String(this.contents, offset + gapLength, length);
175                 }
176                 StringBuffer buf = new StringBuffer();
177                 buf.append(this.contents, offset, this.gapStart - offset);
178                 buf.append(this.contents, this.gapEnd, offset + length - this.gapStart);
179                 return buf.toString();
180         }
181 }
182 /**
183  * @see IBuffer
184  */
185 public IResource getUnderlyingResource() {
186         return this.file;
187 }
188 /**
189  * @see IBuffer
190  */
191 public boolean hasUnsavedChanges() {
192         return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
193 }
194 /**
195  * @see IBuffer
196  */
197 public boolean isClosed() {
198         return (this.flags & F_IS_CLOSED) != 0;
199 }
200 /**
201  * @see IBuffer
202  */
203 public boolean isReadOnly() {
204         if (this.file == null) {
205                 return (this.flags & F_IS_READ_ONLY) != 0;
206         } else {
207                 return this.file.isReadOnly();
208         }
209 }
210 /**
211  * Moves the gap to location and adjust its size to the
212  * anticipated change size. The size represents the expected 
213  * range of the gap that will be filled after the gap has been moved.
214  * Thus the gap is resized to actual size + the specified size and
215  * moved to the given position.
216  */
217 protected void moveAndResizeGap(int position, int size) {
218         char[] content = null;
219         int oldSize = this.gapEnd - this.gapStart;
220         if (size < 0) {
221                 if (oldSize > 0) {
222                         content = new char[this.contents.length - oldSize];
223                         System.arraycopy(this.contents, 0, content, 0, this.gapStart);
224                         System.arraycopy(this.contents, this.gapEnd, content, this.gapStart, content.length - this.gapStart);
225                         this.contents = content;
226                 }
227                 this.gapStart = this.gapEnd = position;
228                 return;
229         }
230         content = new char[this.contents.length + (size - oldSize)];
231         int newGapStart = position;
232         int newGapEnd = newGapStart + size;
233         if (oldSize == 0) {
234                 System.arraycopy(this.contents, 0, content, 0, newGapStart);
235                 System.arraycopy(this.contents, newGapStart, content, newGapEnd, content.length - newGapEnd);
236         } else
237                 if (newGapStart < this.gapStart) {
238                         int delta = this.gapStart - newGapStart;
239                         System.arraycopy(this.contents, 0, content, 0, newGapStart);
240                         System.arraycopy(this.contents, newGapStart, content, newGapEnd, delta);
241                         System.arraycopy(this.contents, this.gapEnd, content, newGapEnd + delta, this.contents.length - this.gapEnd);
242                 } else {
243                         int delta = newGapStart - this.gapStart;
244                         System.arraycopy(this.contents, 0, content, 0, this.gapStart);
245                         System.arraycopy(this.contents, this.gapEnd, content, this.gapStart, delta);
246                         System.arraycopy(this.contents, this.gapEnd + delta, content, newGapEnd, content.length - newGapEnd);
247                 }
248         this.contents = content;
249         this.gapStart = newGapStart;
250         this.gapEnd = newGapEnd;
251 }
252 /**
253  * Notify the listeners that this buffer has changed.
254  * To avoid deadlock, this should not be called in a synchronized block.
255  */
256 protected void notifyChanged(final BufferChangedEvent event) {
257         if (this.changeListeners != null) {
258                 for (int i = 0, size = this.changeListeners.size(); i < size; ++i) {
259                         final IBufferChangedListener listener = (IBufferChangedListener) this.changeListeners.get(i);
260                         Platform.run(new ISafeRunnable() {
261                                 public void handleException(Throwable exception) {
262                                         Util.log(exception, "Exception occurred in listener of buffer change notification"); //$NON-NLS-1$
263                                 }
264                                 public void run() throws Exception {
265                                         listener.bufferChanged(event);
266                                 }
267                         });
268                         
269                 }
270         }
271 }
272 /**
273  * @see IBuffer
274  */
275 public void removeBufferChangedListener(IBufferChangedListener listener) {
276         if (this.changeListeners != null) {
277                 this.changeListeners.remove(listener);
278                 if (this.changeListeners.size() == 0) {
279                         this.changeListeners = null;
280                 }
281         }
282 }
283 /**
284  * Replaces <code>length</code> characters starting from <code>position</code> with <code>text<code>.
285  * After that operation, the gap is placed at the end of the 
286  * inserted <code>text</code>.
287  */
288 public void replace(int position, int length, char[] text) {
289         if (!isReadOnly()) {
290                 int textLength = text == null ? 0 : text.length;
291                 synchronized (this.lock) {
292                         // move gap
293                         moveAndResizeGap(position + length, textLength - length);
294
295                         // overwrite
296                         int min = Math.min(textLength, length);
297                         if (min > 0) {
298                                 System.arraycopy(text, 0, this.contents, position, min);
299                         }
300                         if (length > textLength) {
301                                 // enlarge the gap
302                                 this.gapStart -= length - textLength;
303                         } else if (textLength > length) {
304                                 // shrink gap
305                                 this.gapStart += textLength - length;
306                                 System.arraycopy(text, 0, this.contents, position, textLength);
307                         }
308                 }
309                 this.flags |= F_HAS_UNSAVED_CHANGES;
310                 String string = null;
311                 if (textLength > 0) {
312                         string = new String(text);
313                 }
314                 notifyChanged(new BufferChangedEvent(this, position, length, string));
315         }
316 }
317 /**
318  * Replaces <code>length</code> characters starting from <code>position</code> with <code>text<code>.
319  * After that operation, the gap is placed at the end of the 
320  * inserted <code>text</code>.
321  */
322 public void replace(int position, int length, String text) {
323         this.replace(position, length, text == null ? null : text.toCharArray());
324 }
325 /**
326  * @see IBuffer
327  */
328 public void save(IProgressMonitor progress, boolean force) throws JavaModelException {
329
330         // determine if saving is required 
331         if (isReadOnly() || this.file == null) {
332                 return;
333         }
334         synchronized (this.lock) {
335                 if (!hasUnsavedChanges())
336                         return;
337                         
338                 // use a platform operation to update the resource contents
339                 try {
340 //                      String encoding = ((IJavaElement)this.owner).getJavaProject().getOption(PHPCore.CORE_ENCODING, true);
341                         String encoding = null;
342                         String contents = this.getContents();
343                         if (contents == null) return;
344                         byte[] bytes = encoding == null 
345                                 ? contents.getBytes() 
346                                 : contents.getBytes(encoding);
347                         ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
348
349                         this.file.setContents(
350                                 stream, 
351                                 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY, 
352                                 null);
353                 } catch (IOException e) {
354                         throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
355                 } catch (CoreException e) {
356                         throw new JavaModelException(e);
357                 }
358
359                 // the resource no longer has unsaved changes
360                 this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
361         }
362 }
363 /**
364  * @see IBuffer
365  */
366 public void setContents(char[] newContents) {
367         // allow special case for first initialization 
368         // after creation by buffer factory
369         if (this.contents == null) {
370                 this.contents = newContents;
371                 this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
372                 return;
373         }
374         
375         if (!isReadOnly()) {
376                 String string = null;
377                 if (newContents != null) {
378                         string = new String(newContents);
379                 }
380                 BufferChangedEvent event = new BufferChangedEvent(this, 0, this.getLength(), string);
381                 synchronized (this.lock) {
382                         this.contents = newContents;
383                         this.flags |= F_HAS_UNSAVED_CHANGES;
384                         this.gapStart = -1;
385                         this.gapEnd = -1;
386                 }
387                 notifyChanged(event);
388         }
389 }
390 /**
391  * @see IBuffer
392  */
393 public void setContents(String newContents) {
394         this.setContents(newContents.toCharArray());
395 }
396 /**
397  * Sets this <code>Buffer</code> to be read only.
398  */
399 protected void setReadOnly(boolean readOnly) {
400         if (readOnly) {
401                 this.flags |= F_IS_READ_ONLY;
402         } else {
403                 this.flags &= ~(F_IS_READ_ONLY);
404         }
405 }
406 public String toString() {
407         StringBuffer buffer = new StringBuffer();
408 //      buffer.append("Owner: " + ((JavaElement)this.owner).toStringWithAncestors()); //$NON-NLS-1$
409         buffer.append("Owner: " + (this.owner).toString()); //$NON-NLS-1$
410         buffer.append("\nHas unsaved changes: " + this.hasUnsavedChanges()); //$NON-NLS-1$
411         buffer.append("\nIs readonly: " + this.isReadOnly()); //$NON-NLS-1$
412         buffer.append("\nIs closed: " + this.isClosed()); //$NON-NLS-1$
413         buffer.append("\nContents:\n"); //$NON-NLS-1$
414         char[] contents = this.getCharacters();
415         if (contents == null) {
416                 buffer.append("<null>"); //$NON-NLS-1$
417         } else {
418                 int length = contents.length;
419                 for (int i = 0; i < length; i++) {
420                         char car = contents[i];
421                         switch (car) {
422                                 case '\n': 
423                                         buffer.append("\\n\n"); //$NON-NLS-1$
424                                         break;
425                                 case '\r':
426                                         if (i < length-1 && this.contents[i+1] == '\n') {
427                                                 buffer.append("\\r\\n\n"); //$NON-NLS-1$
428                                                 i++;
429                                         } else {
430                                                 buffer.append("\\r\n"); //$NON-NLS-1$
431                                         }
432                                         break;
433                                 default:
434                                         buffer.append(car);
435                                         break;
436                         }
437                 }
438         }
439         return buffer.toString();
440 }
441 }