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