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