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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package net.sourceforge.phpdt.internal.core;
13 import java.io.ByteArrayInputStream;
14 import java.io.IOException;
15 import java.util.ArrayList;
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;
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;
35 public class Buffer implements IBuffer {
40 protected char[] contents;
42 protected ArrayList changeListeners;
44 protected IOpenable owner;
46 protected int gapStart = -1;
48 protected int gapEnd = -1;
50 protected Object lock = new Object();
52 protected static final int F_HAS_UNSAVED_CHANGES = 1;
54 protected static final int F_IS_READ_ONLY = 2;
56 protected static final int F_IS_CLOSED = 4;
59 * Creates a new buffer on an underlying resource.
61 protected Buffer(IFile file, IOpenable owner, boolean readOnly) {
65 setReadOnly(readOnly);
72 public void addBufferChangedListener(IBufferChangedListener listener) {
73 if (this.changeListeners == null) {
74 this.changeListeners = new ArrayList(5);
76 if (!this.changeListeners.contains(listener)) {
77 this.changeListeners.add(listener);
82 * Append the <code>text</code> to the actual content, the gap is moved to
83 * the end of the <code>text</code>.
85 public void append(char[] text) {
87 if (text == null || text.length == 0) {
90 int length = getLength();
91 moveAndResizeGap(length, text.length);
92 System.arraycopy(text, 0, this.contents, length, text.length);
93 this.gapStart += text.length;
94 this.flags |= F_HAS_UNSAVED_CHANGES;
95 notifyChanged(new BufferChangedEvent(this, length, 0, new String(
101 * Append the <code>text</code> to the actual content, the gap is moved to
102 * the end of the <code>text</code>.
104 public void append(String text) {
108 this.append(text.toCharArray());
114 public void close() throws IllegalArgumentException {
115 BufferChangedEvent event = null;
116 synchronized (this.lock) {
119 event = new BufferChangedEvent(this, 0, 0, null);
120 this.contents = null;
121 this.flags |= F_IS_CLOSED;
123 notifyChanged(event); // notify outside of synchronized block
124 this.changeListeners = null;
130 public char getChar(int position) {
131 synchronized (this.lock) {
132 if (position < this.gapStart) {
133 return this.contents[position];
135 int gapLength = this.gapEnd - this.gapStart;
136 return this.contents[position + gapLength];
143 public char[] getCharacters() {
144 if (this.contents == null)
146 synchronized (this.lock) {
147 if (this.gapStart < 0) {
148 return this.contents;
150 int length = this.contents.length;
151 char[] newContents = new char[length - this.gapEnd + this.gapStart];
152 System.arraycopy(this.contents, 0, newContents, 0, this.gapStart);
153 System.arraycopy(this.contents, this.gapEnd, newContents,
154 this.gapStart, length - this.gapEnd);
162 public String getContents() {
163 char[] chars = this.getCharacters();
166 return new String(chars);
172 public int getLength() {
173 synchronized (this.lock) {
174 int length = this.gapEnd - this.gapStart;
175 return (this.contents.length - length);
182 public IOpenable getOwner() {
189 public String getText(int offset, int length) {
190 if (this.contents == null)
191 return ""; //$NON-NLS-1$
192 synchronized (this.lock) {
193 if (offset + length < this.gapStart)
194 return new String(this.contents, offset, length);
195 if (this.gapStart < offset) {
196 int gapLength = this.gapEnd - this.gapStart;
197 return new String(this.contents, offset + gapLength, length);
199 StringBuffer buf = new StringBuffer();
200 buf.append(this.contents, offset, this.gapStart - offset);
201 buf.append(this.contents, this.gapEnd, offset + length
203 return buf.toString();
210 public IResource getUnderlyingResource() {
217 public boolean hasUnsavedChanges() {
218 return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
224 public boolean isClosed() {
225 return (this.flags & F_IS_CLOSED) != 0;
231 public boolean isReadOnly() {
232 if (this.file == null) {
233 return (this.flags & F_IS_READ_ONLY) != 0;
235 return this.file.isReadOnly();
240 * Moves the gap to location and adjust its size to the anticipated change
241 * size. The size represents the expected range of the gap that will be
242 * filled after the gap has been moved. Thus the gap is resized to actual
243 * size + the specified size and moved to the given position.
245 protected void moveAndResizeGap(int position, int size) {
246 char[] content = null;
247 int oldSize = this.gapEnd - this.gapStart;
250 content = new char[this.contents.length - oldSize];
251 System.arraycopy(this.contents, 0, content, 0, this.gapStart);
252 System.arraycopy(this.contents, this.gapEnd, content,
253 this.gapStart, content.length - this.gapStart);
254 this.contents = content;
256 this.gapStart = this.gapEnd = position;
259 content = new char[this.contents.length + (size - oldSize)];
260 int newGapStart = position;
261 int newGapEnd = newGapStart + size;
263 System.arraycopy(this.contents, 0, content, 0, newGapStart);
264 System.arraycopy(this.contents, newGapStart, content, newGapEnd,
265 content.length - newGapEnd);
266 } else if (newGapStart < this.gapStart) {
267 int delta = this.gapStart - newGapStart;
268 System.arraycopy(this.contents, 0, content, 0, newGapStart);
269 System.arraycopy(this.contents, newGapStart, content, newGapEnd,
271 System.arraycopy(this.contents, this.gapEnd, content, newGapEnd
272 + delta, this.contents.length - this.gapEnd);
274 int delta = newGapStart - this.gapStart;
275 System.arraycopy(this.contents, 0, content, 0, this.gapStart);
276 System.arraycopy(this.contents, this.gapEnd, content,
277 this.gapStart, delta);
278 System.arraycopy(this.contents, this.gapEnd + delta, content,
279 newGapEnd, content.length - newGapEnd);
281 this.contents = content;
282 this.gapStart = newGapStart;
283 this.gapEnd = newGapEnd;
287 * Notify the listeners that this buffer has changed. To avoid deadlock,
288 * this should not be called in a synchronized block.
290 protected void notifyChanged(final BufferChangedEvent event) {
291 if (this.changeListeners != null) {
292 for (int i = 0, size = this.changeListeners.size(); i < size; ++i) {
293 final IBufferChangedListener listener = (IBufferChangedListener) this.changeListeners
295 Platform.run(new ISafeRunnable() {
296 public void handleException(Throwable exception) {
299 "Exception occurred in listener of buffer change notification"); //$NON-NLS-1$
302 public void run() throws Exception {
303 listener.bufferChanged(event);
314 public void removeBufferChangedListener(IBufferChangedListener listener) {
315 if (this.changeListeners != null) {
316 this.changeListeners.remove(listener);
317 if (this.changeListeners.size() == 0) {
318 this.changeListeners = null;
324 * Replaces <code>length</code> characters starting from
325 * <code>position</code> with <code>text<code>.
326 * After that operation, the gap is placed at the end of the
327 * inserted <code>text</code>.
329 public void replace(int position, int length, char[] text) {
331 int textLength = text == null ? 0 : text.length;
332 synchronized (this.lock) {
334 moveAndResizeGap(position + length, textLength - length);
337 int min = Math.min(textLength, length);
339 System.arraycopy(text, 0, this.contents, position, min);
341 if (length > textLength) {
343 this.gapStart -= length - textLength;
344 } else if (textLength > length) {
346 this.gapStart += textLength - length;
347 System.arraycopy(text, 0, this.contents, position,
351 this.flags |= F_HAS_UNSAVED_CHANGES;
352 String string = null;
353 if (textLength > 0) {
354 string = new String(text);
356 notifyChanged(new BufferChangedEvent(this, position, length, string));
361 * Replaces <code>length</code> characters starting from
362 * <code>position</code> with <code>text<code>.
363 * After that operation, the gap is placed at the end of the
364 * inserted <code>text</code>.
366 public void replace(int position, int length, String text) {
368 .replace(position, length, text == null ? null : text
375 public void save(IProgressMonitor progress, boolean force)
376 throws JavaModelException {
378 // determine if saving is required
379 if (isReadOnly() || this.file == null) {
382 synchronized (this.lock) {
383 if (!hasUnsavedChanges())
386 // use a platform operation to update the resource contents
389 // ((IJavaElement)this.owner).getJavaProject().getOption(PHPCore.CORE_ENCODING,
391 String encoding = null;
392 String contents = this.getContents();
393 if (contents == null)
395 byte[] bytes = encoding == null ? contents.getBytes()
396 : contents.getBytes(encoding);
397 ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
400 .setContents(stream, force ? IResource.FORCE
401 | IResource.KEEP_HISTORY
402 : IResource.KEEP_HISTORY, null);
403 } catch (IOException e) {
404 throw new JavaModelException(e,
405 IJavaModelStatusConstants.IO_EXCEPTION);
406 } catch (CoreException e) {
407 throw new JavaModelException(e);
410 // the resource no longer has unsaved changes
411 this.flags &= ~(F_HAS_UNSAVED_CHANGES);
418 public void setContents(char[] newContents) {
419 // allow special case for first initialization
420 // after creation by buffer factory
421 if (this.contents == null) {
422 this.contents = newContents;
423 this.flags &= ~(F_HAS_UNSAVED_CHANGES);
428 String string = null;
429 if (newContents != null) {
430 string = new String(newContents);
432 BufferChangedEvent event = new BufferChangedEvent(this, 0, this
433 .getLength(), string);
434 synchronized (this.lock) {
435 this.contents = newContents;
436 this.flags |= F_HAS_UNSAVED_CHANGES;
440 notifyChanged(event);
447 public void setContents(String newContents) {
448 this.setContents(newContents.toCharArray());
452 * Sets this <code>Buffer</code> to be read only.
454 protected void setReadOnly(boolean readOnly) {
456 this.flags |= F_IS_READ_ONLY;
458 this.flags &= ~(F_IS_READ_ONLY);
462 public String toString() {
463 StringBuffer buffer = new StringBuffer();
464 // buffer.append("Owner: " +
465 // ((JavaElement)this.owner).toStringWithAncestors()); //$NON-NLS-1$
466 buffer.append("Owner: " + (this.owner).toString()); //$NON-NLS-1$
467 buffer.append("\nHas unsaved changes: " + this.hasUnsavedChanges()); //$NON-NLS-1$
468 buffer.append("\nIs readonly: " + this.isReadOnly()); //$NON-NLS-1$
469 buffer.append("\nIs closed: " + this.isClosed()); //$NON-NLS-1$
470 buffer.append("\nContents:\n"); //$NON-NLS-1$
471 char[] contents = this.getCharacters();
472 if (contents == null) {
473 buffer.append("<null>"); //$NON-NLS-1$
475 int length = contents.length;
476 for (int i = 0; i < length; i++) {
477 char car = contents[i];
480 buffer.append("\\n\n"); //$NON-NLS-1$
483 if (i < length - 1 && this.contents[i + 1] == '\n') {
484 buffer.append("\\r\\n\n"); //$NON-NLS-1$
487 buffer.append("\\r\n"); //$NON-NLS-1$
496 return buffer.toString();