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 {
38 protected char[] contents;
39 protected ArrayList changeListeners;
40 protected IOpenable owner;
41 protected int gapStart= -1;
42 protected int gapEnd= -1;
44 protected Object lock= new Object();
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;
51 * Creates a new buffer on an underlying resource.
53 protected Buffer(IFile file, IOpenable owner, boolean readOnly) {
57 setReadOnly(readOnly);
63 public void addBufferChangedListener(IBufferChangedListener listener) {
64 if (this.changeListeners == null) {
65 this.changeListeners = new ArrayList(5);
67 if (!this.changeListeners.contains(listener)) {
68 this.changeListeners.add(listener);
72 * Append the <code>text</code> to the actual content, the gap is moved
73 * to the end of the <code>text</code>.
75 public void append(char[] text) {
77 if (text == null || text.length == 0) {
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)));
89 * Append the <code>text</code> to the actual content, the gap is moved
90 * to the end of the <code>text</code>.
92 public void append(String text) {
96 this.append(text.toCharArray());
101 public void close() throws IllegalArgumentException {
102 BufferChangedEvent event = null;
103 synchronized (this.lock) {
106 event = new BufferChangedEvent(this, 0, 0, null);
107 this.contents = null;
108 this.flags |= F_IS_CLOSED;
110 notifyChanged(event); // notify outside of synchronized block
111 this.changeListeners = null;
116 public char getChar(int position) {
117 synchronized (this.lock) {
118 if (position < this.gapStart) {
119 return this.contents[position];
121 int gapLength = this.gapEnd - this.gapStart;
122 return this.contents[position + gapLength];
128 public char[] getCharacters() {
129 if (this.contents == null) return null;
130 synchronized (this.lock) {
131 if (this.gapStart < 0) {
132 return this.contents;
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);
144 public String getContents() {
145 char[] chars = this.getCharacters();
146 if (chars == null) return null;
147 return new String(chars);
152 public int getLength() {
153 synchronized (this.lock) {
154 int length = this.gapEnd - this.gapStart;
155 return (this.contents.length - length);
161 public IOpenable getOwner() {
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);
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();
186 public IResource getUnderlyingResource() {
192 public boolean hasUnsavedChanges() {
193 return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
198 public boolean isClosed() {
199 return (this.flags & F_IS_CLOSED) != 0;
204 public boolean isReadOnly() {
205 if (this.file == null) {
206 return (this.flags & F_IS_READ_ONLY) != 0;
208 return this.file.isReadOnly();
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.
218 protected void moveAndResizeGap(int position, int size) {
219 char[] content = null;
220 int oldSize = this.gapEnd - this.gapStart;
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;
228 this.gapStart = this.gapEnd = position;
231 content = new char[this.contents.length + (size - oldSize)];
232 int newGapStart = position;
233 int newGapEnd = newGapStart + size;
235 System.arraycopy(this.contents, 0, content, 0, newGapStart);
236 System.arraycopy(this.contents, newGapStart, content, newGapEnd, content.length - newGapEnd);
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);
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);
249 this.contents = content;
250 this.gapStart = newGapStart;
251 this.gapEnd = newGapEnd;
254 * Notify the listeners that this buffer has changed.
255 * To avoid deadlock, this should not be called in a synchronized block.
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$
265 public void run() throws Exception {
266 listener.bufferChanged(event);
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;
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>.
289 public void replace(int position, int length, char[] text) {
291 int textLength = text == null ? 0 : text.length;
292 synchronized (this.lock) {
294 moveAndResizeGap(position + length, textLength - length);
297 int min = Math.min(textLength, length);
299 System.arraycopy(text, 0, this.contents, position, min);
301 if (length > textLength) {
303 this.gapStart -= length - textLength;
304 } else if (textLength > length) {
306 this.gapStart += textLength - length;
307 System.arraycopy(text, 0, this.contents, position, textLength);
310 this.flags |= F_HAS_UNSAVED_CHANGES;
311 String string = null;
312 if (textLength > 0) {
313 string = new String(text);
315 notifyChanged(new BufferChangedEvent(this, position, length, string));
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>.
323 public void replace(int position, int length, String text) {
324 this.replace(position, length, text == null ? null : text.toCharArray());
329 public void save(IProgressMonitor progress, boolean force) throws JavaModelException {
331 // determine if saving is required
332 if (isReadOnly() || this.file == null) {
335 synchronized (this.lock) {
336 if (!hasUnsavedChanges())
339 // use a platform operation to update the resource contents
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);
350 this.file.setContents(
352 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
354 } catch (IOException e) {
355 throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
356 } catch (CoreException e) {
357 throw new JavaModelException(e);
360 // the resource no longer has unsaved changes
361 this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
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);
377 String string = null;
378 if (newContents != null) {
379 string = new String(newContents);
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;
388 notifyChanged(event);
394 public void setContents(String newContents) {
395 this.setContents(newContents.toCharArray());
398 * Sets this <code>Buffer</code> to be read only.
400 protected void setReadOnly(boolean readOnly) {
402 this.flags |= F_IS_READ_ONLY;
404 this.flags &= ~(F_IS_READ_ONLY);
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$
419 int length = contents.length;
420 for (int i = 0; i < length; i++) {
421 char car = contents[i];
424 buffer.append("\\n\n"); //$NON-NLS-1$
427 if (i < length-1 && this.contents[i+1] == '\n') {
428 buffer.append("\\r\\n\n"); //$NON-NLS-1$
431 buffer.append("\\r\n"); //$NON-NLS-1$
440 return buffer.toString();