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;
31 import org.eclipse.core.runtime.SafeRunner;
36 public class Buffer implements IBuffer {
41 protected char[] contents;
43 protected ArrayList changeListeners;
45 protected IOpenable owner;
47 protected int gapStart = -1;
49 protected int gapEnd = -1;
51 protected Object lock = new Object();
53 protected static final int F_HAS_UNSAVED_CHANGES = 1;
55 protected static final int F_IS_READ_ONLY = 2;
57 protected static final int F_IS_CLOSED = 4;
60 * Creates a new buffer on an underlying resource.
62 protected Buffer(IFile file, IOpenable owner, boolean readOnly) {
66 setReadOnly(readOnly);
73 public void addBufferChangedListener(IBufferChangedListener listener) {
74 if (this.changeListeners == null) {
75 this.changeListeners = new ArrayList(5);
77 if (!this.changeListeners.contains(listener)) {
78 this.changeListeners.add(listener);
83 * Append the <code>text</code> to the actual content, the gap is moved to
84 * the end of the <code>text</code>.
86 public void append(char[] text) {
88 if (text == null || text.length == 0) {
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(
102 * Append the <code>text</code> to the actual content, the gap is moved to
103 * the end of the <code>text</code>.
105 public void append(String text) {
109 this.append(text.toCharArray());
115 public void close() throws IllegalArgumentException {
116 BufferChangedEvent event = null;
117 synchronized (this.lock) {
120 event = new BufferChangedEvent(this, 0, 0, null);
121 this.contents = null;
122 this.flags |= F_IS_CLOSED;
124 notifyChanged(event); // notify outside of synchronized block
125 this.changeListeners = null;
131 public char getChar(int position) {
132 synchronized (this.lock) {
133 if (position < this.gapStart) {
134 return this.contents[position];
136 int gapLength = this.gapEnd - this.gapStart;
137 return this.contents[position + gapLength];
144 public char[] getCharacters() {
145 if (this.contents == null)
147 synchronized (this.lock) {
148 if (this.gapStart < 0) {
149 return this.contents;
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);
163 public String getContents() {
164 char[] chars = this.getCharacters();
167 return new String(chars);
173 public int getLength() {
174 synchronized (this.lock) {
175 int length = this.gapEnd - this.gapStart;
176 return (this.contents.length - length);
183 public IOpenable getOwner() {
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);
200 StringBuffer buf = new StringBuffer();
201 buf.append(this.contents, offset, this.gapStart - offset);
202 buf.append(this.contents, this.gapEnd, offset + length
204 return buf.toString();
211 public IResource getUnderlyingResource() {
218 public boolean hasUnsavedChanges() {
219 return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
225 public boolean isClosed() {
226 return (this.flags & F_IS_CLOSED) != 0;
232 public boolean isReadOnly() {
233 if (this.file == null) {
234 return (this.flags & F_IS_READ_ONLY) != 0;
236 return this.file.isReadOnly();
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.
246 protected void moveAndResizeGap(int position, int size) {
247 char[] content = null;
248 int oldSize = this.gapEnd - this.gapStart;
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;
257 this.gapStart = this.gapEnd = position;
260 content = new char[this.contents.length + (size - oldSize)];
261 int newGapStart = position;
262 int newGapEnd = newGapStart + size;
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,
272 System.arraycopy(this.contents, this.gapEnd, content, newGapEnd
273 + delta, this.contents.length - this.gapEnd);
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);
282 this.contents = content;
283 this.gapStart = newGapStart;
284 this.gapEnd = newGapEnd;
288 * Notify the listeners that this buffer has changed. To avoid deadlock,
289 * this should not be called in a synchronized block.
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
296 SafeRunner.run(new ISafeRunnable() {
297 public void handleException(Throwable exception) {
300 "Exception occurred in listener of buffer change notification"); //$NON-NLS-1$
303 public void run() throws Exception {
304 listener.bufferChanged(event);
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;
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>.
330 public void replace(int position, int length, char[] text) {
332 int textLength = text == null ? 0 : text.length;
333 synchronized (this.lock) {
335 moveAndResizeGap(position + length, textLength - length);
338 int min = Math.min(textLength, length);
340 System.arraycopy(text, 0, this.contents, position, min);
342 if (length > textLength) {
344 this.gapStart -= length - textLength;
345 } else if (textLength > length) {
347 this.gapStart += textLength - length;
348 System.arraycopy(text, 0, this.contents, position,
352 this.flags |= F_HAS_UNSAVED_CHANGES;
353 String string = null;
354 if (textLength > 0) {
355 string = new String(text);
357 notifyChanged(new BufferChangedEvent(this, position, length, string));
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>.
367 public void replace(int position, int length, String text) {
369 .replace(position, length, text == null ? null : text
376 public void save(IProgressMonitor progress, boolean force)
377 throws JavaModelException {
379 // determine if saving is required
380 if (isReadOnly() || this.file == null) {
383 synchronized (this.lock) {
384 if (!hasUnsavedChanges())
387 // use a platform operation to update the resource contents
390 // ((IJavaElement)this.owner).getJavaProject().getOption(PHPCore.CORE_ENCODING,
392 String encoding = null;
393 String contents = this.getContents();
394 if (contents == null)
396 byte[] bytes = encoding == null ? contents.getBytes()
397 : contents.getBytes(encoding);
398 ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
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);
411 // the resource no longer has unsaved changes
412 this.flags &= ~(F_HAS_UNSAVED_CHANGES);
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);
429 String string = null;
430 if (newContents != null) {
431 string = new String(newContents);
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;
441 notifyChanged(event);
448 public void setContents(String newContents) {
449 this.setContents(newContents.toCharArray());
453 * Sets this <code>Buffer</code> to be read only.
455 protected void setReadOnly(boolean readOnly) {
457 this.flags |= F_IS_READ_ONLY;
459 this.flags &= ~(F_IS_READ_ONLY);
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$
476 int length = contents.length;
477 for (int i = 0; i < length; i++) {
478 char car = contents[i];
481 buffer.append("\\n\n"); //$NON-NLS-1$
484 if (i < length - 1 && this.contents[i + 1] == '\n') {
485 buffer.append("\\r\\n\n"); //$NON-NLS-1$
488 buffer.append("\\r\n"); //$NON-NLS-1$
497 return buffer.toString();