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;
24 import org.eclipse.core.resources.IFile;
25 import org.eclipse.core.resources.IResource;
26 import org.eclipse.core.runtime.CoreException;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.ISafeRunnable;
29 import org.eclipse.core.runtime.Platform;
34 public class Buffer implements IBuffer {
37 protected char[] contents;
38 protected ArrayList changeListeners;
39 protected IOpenable owner;
40 protected int gapStart= -1;
41 protected int gapEnd= -1;
43 protected Object lock= new Object();
45 protected static final int F_HAS_UNSAVED_CHANGES= 1;
46 protected static final int F_IS_READ_ONLY= 2;
47 protected static final int F_IS_CLOSED= 4;
50 * Creates a new buffer on an underlying resource.
52 protected Buffer(IFile file, IOpenable owner, boolean readOnly) {
56 setReadOnly(readOnly);
62 public void addBufferChangedListener(IBufferChangedListener listener) {
63 if (this.changeListeners == null) {
64 this.changeListeners = new ArrayList(5);
66 if (!this.changeListeners.contains(listener)) {
67 this.changeListeners.add(listener);
71 * Append the <code>text</code> to the actual content, the gap is moved
72 * to the end of the <code>text</code>.
74 public void append(char[] text) {
76 if (text == null || text.length == 0) {
79 int length = getLength();
80 moveAndResizeGap(length, text.length);
81 System.arraycopy(text, 0, this.contents, length, text.length);
82 this.gapStart += text.length;
83 this.flags |= F_HAS_UNSAVED_CHANGES;
84 notifyChanged(new BufferChangedEvent(this, length, 0, new String(text)));
88 * Append the <code>text</code> to the actual content, the gap is moved
89 * to the end of the <code>text</code>.
91 public void append(String text) {
95 this.append(text.toCharArray());
100 public void close() throws IllegalArgumentException {
101 BufferChangedEvent event = null;
102 synchronized (this.lock) {
105 event = new BufferChangedEvent(this, 0, 0, null);
106 this.contents = null;
107 this.flags |= F_IS_CLOSED;
109 notifyChanged(event); // notify outside of synchronized block
110 this.changeListeners = null;
115 public char getChar(int position) {
116 synchronized (this.lock) {
117 if (position < this.gapStart) {
118 return this.contents[position];
120 int gapLength = this.gapEnd - this.gapStart;
121 return this.contents[position + gapLength];
127 public char[] getCharacters() {
128 if (this.contents == null) return null;
129 synchronized (this.lock) {
130 if (this.gapStart < 0) {
131 return this.contents;
133 int length = this.contents.length;
134 char[] newContents = new char[length - this.gapEnd + this.gapStart];
135 System.arraycopy(this.contents, 0, newContents, 0, this.gapStart);
136 System.arraycopy(this.contents, this.gapEnd, newContents, this.gapStart, length - this.gapEnd);
143 public String getContents() {
144 char[] chars = this.getCharacters();
145 if (chars == null) return null;
146 return new String(chars);
151 public int getLength() {
152 synchronized (this.lock) {
153 int length = this.gapEnd - this.gapStart;
154 return (this.contents.length - length);
160 public IOpenable getOwner() {
166 public String getText(int offset, int length) {
167 if (this.contents == null)
168 return ""; //$NON-NLS-1$
169 synchronized (this.lock) {
170 if (offset + length < this.gapStart)
171 return new String(this.contents, offset, length);
172 if (this.gapStart < offset) {
173 int gapLength = this.gapEnd - this.gapStart;
174 return new String(this.contents, offset + gapLength, length);
176 StringBuffer buf = new StringBuffer();
177 buf.append(this.contents, offset, this.gapStart - offset);
178 buf.append(this.contents, this.gapEnd, offset + length - this.gapStart);
179 return buf.toString();
185 public IResource getUnderlyingResource() {
191 public boolean hasUnsavedChanges() {
192 return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
197 public boolean isClosed() {
198 return (this.flags & F_IS_CLOSED) != 0;
203 public boolean isReadOnly() {
204 if (this.file == null) {
205 return (this.flags & F_IS_READ_ONLY) != 0;
207 return this.file.isReadOnly();
211 * Moves the gap to location and adjust its size to the
212 * anticipated change size. The size represents the expected
213 * range of the gap that will be filled after the gap has been moved.
214 * Thus the gap is resized to actual size + the specified size and
215 * moved to the given position.
217 protected void moveAndResizeGap(int position, int size) {
218 char[] content = null;
219 int oldSize = this.gapEnd - this.gapStart;
222 content = new char[this.contents.length - oldSize];
223 System.arraycopy(this.contents, 0, content, 0, this.gapStart);
224 System.arraycopy(this.contents, this.gapEnd, content, this.gapStart, content.length - this.gapStart);
225 this.contents = content;
227 this.gapStart = this.gapEnd = position;
230 content = new char[this.contents.length + (size - oldSize)];
231 int newGapStart = position;
232 int newGapEnd = newGapStart + size;
234 System.arraycopy(this.contents, 0, content, 0, newGapStart);
235 System.arraycopy(this.contents, newGapStart, content, newGapEnd, content.length - newGapEnd);
237 if (newGapStart < this.gapStart) {
238 int delta = this.gapStart - newGapStart;
239 System.arraycopy(this.contents, 0, content, 0, newGapStart);
240 System.arraycopy(this.contents, newGapStart, content, newGapEnd, delta);
241 System.arraycopy(this.contents, this.gapEnd, content, newGapEnd + delta, this.contents.length - this.gapEnd);
243 int delta = newGapStart - this.gapStart;
244 System.arraycopy(this.contents, 0, content, 0, this.gapStart);
245 System.arraycopy(this.contents, this.gapEnd, content, this.gapStart, delta);
246 System.arraycopy(this.contents, this.gapEnd + delta, content, newGapEnd, content.length - newGapEnd);
248 this.contents = content;
249 this.gapStart = newGapStart;
250 this.gapEnd = newGapEnd;
253 * Notify the listeners that this buffer has changed.
254 * To avoid deadlock, this should not be called in a synchronized block.
256 protected void notifyChanged(final BufferChangedEvent event) {
257 if (this.changeListeners != null) {
258 for (int i = 0, size = this.changeListeners.size(); i < size; ++i) {
259 final IBufferChangedListener listener = (IBufferChangedListener) this.changeListeners.get(i);
260 Platform.run(new ISafeRunnable() {
261 public void handleException(Throwable exception) {
262 Util.log(exception, "Exception occurred in listener of buffer change notification"); //$NON-NLS-1$
264 public void run() throws Exception {
265 listener.bufferChanged(event);
275 public void removeBufferChangedListener(IBufferChangedListener listener) {
276 if (this.changeListeners != null) {
277 this.changeListeners.remove(listener);
278 if (this.changeListeners.size() == 0) {
279 this.changeListeners = null;
284 * Replaces <code>length</code> characters starting from <code>position</code> with <code>text<code>.
285 * After that operation, the gap is placed at the end of the
286 * inserted <code>text</code>.
288 public void replace(int position, int length, char[] text) {
290 int textLength = text == null ? 0 : text.length;
291 synchronized (this.lock) {
293 moveAndResizeGap(position + length, textLength - length);
296 int min = Math.min(textLength, length);
298 System.arraycopy(text, 0, this.contents, position, min);
300 if (length > textLength) {
302 this.gapStart -= length - textLength;
303 } else if (textLength > length) {
305 this.gapStart += textLength - length;
306 System.arraycopy(text, 0, this.contents, position, textLength);
309 this.flags |= F_HAS_UNSAVED_CHANGES;
310 String string = null;
311 if (textLength > 0) {
312 string = new String(text);
314 notifyChanged(new BufferChangedEvent(this, position, length, string));
318 * Replaces <code>length</code> characters starting from <code>position</code> with <code>text<code>.
319 * After that operation, the gap is placed at the end of the
320 * inserted <code>text</code>.
322 public void replace(int position, int length, String text) {
323 this.replace(position, length, text == null ? null : text.toCharArray());
328 public void save(IProgressMonitor progress, boolean force) throws JavaModelException {
330 // determine if saving is required
331 if (isReadOnly() || this.file == null) {
334 synchronized (this.lock) {
335 if (!hasUnsavedChanges())
338 // use a platform operation to update the resource contents
340 // String encoding = ((IJavaElement)this.owner).getJavaProject().getOption(PHPCore.CORE_ENCODING, true);
341 String encoding = null;
342 String contents = this.getContents();
343 if (contents == null) return;
344 byte[] bytes = encoding == null
345 ? contents.getBytes()
346 : contents.getBytes(encoding);
347 ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
349 this.file.setContents(
351 force ? IResource.FORCE | IResource.KEEP_HISTORY : IResource.KEEP_HISTORY,
353 } catch (IOException e) {
354 throw new JavaModelException(e, IJavaModelStatusConstants.IO_EXCEPTION);
355 } catch (CoreException e) {
356 throw new JavaModelException(e);
359 // the resource no longer has unsaved changes
360 this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
366 public void setContents(char[] newContents) {
367 // allow special case for first initialization
368 // after creation by buffer factory
369 if (this.contents == null) {
370 this.contents = newContents;
371 this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
376 String string = null;
377 if (newContents != null) {
378 string = new String(newContents);
380 BufferChangedEvent event = new BufferChangedEvent(this, 0, this.getLength(), string);
381 synchronized (this.lock) {
382 this.contents = newContents;
383 this.flags |= F_HAS_UNSAVED_CHANGES;
387 notifyChanged(event);
393 public void setContents(String newContents) {
394 this.setContents(newContents.toCharArray());
397 * Sets this <code>Buffer</code> to be read only.
399 protected void setReadOnly(boolean readOnly) {
401 this.flags |= F_IS_READ_ONLY;
403 this.flags &= ~(F_IS_READ_ONLY);
406 public String toString() {
407 StringBuffer buffer = new StringBuffer();
408 // buffer.append("Owner: " + ((JavaElement)this.owner).toStringWithAncestors()); //$NON-NLS-1$
409 buffer.append("Owner: " + (this.owner).toString()); //$NON-NLS-1$
410 buffer.append("\nHas unsaved changes: " + this.hasUnsavedChanges()); //$NON-NLS-1$
411 buffer.append("\nIs readonly: " + this.isReadOnly()); //$NON-NLS-1$
412 buffer.append("\nIs closed: " + this.isClosed()); //$NON-NLS-1$
413 buffer.append("\nContents:\n"); //$NON-NLS-1$
414 char[] contents = this.getCharacters();
415 if (contents == null) {
416 buffer.append("<null>"); //$NON-NLS-1$
418 int length = contents.length;
419 for (int i = 0; i < length; i++) {
420 char car = contents[i];
423 buffer.append("\\n\n"); //$NON-NLS-1$
426 if (i < length-1 && this.contents[i+1] == '\n') {
427 buffer.append("\\r\\n\n"); //$NON-NLS-1$
430 buffer.append("\\r\n"); //$NON-NLS-1$
439 return buffer.toString();