Index: editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/ExternalFileCharSequenceProvider.java |
=================================================================== |
--- editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/ExternalFileCharSequenceProvider.java (revision 0) |
+++ editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/ExternalFileCharSequenceProvider.java (revision 0) |
@@ -0,0 +1,489 @@ |
+/* |
+ * Copyright (c) 2011, the Dart project authors. |
+ * |
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except |
+ * in compliance with the License. You may obtain a copy of the License at |
+ * |
+ * http://www.eclipse.org/legal/epl-v10.html |
+ * |
+ * Unless required by applicable law or agreed to in writing, software distributed under the License |
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
+ * or implied. See the License for the specific language governing permissions and limitations under |
+ * the License. |
+ */ |
+package com.google.dart.tools.search.internal.core.text; |
+ |
+import org.eclipse.core.runtime.CoreException; |
+ |
+import java.io.File; |
+import java.io.FileInputStream; |
+import java.io.IOException; |
+import java.io.InputStream; |
+import java.io.InputStreamReader; |
+import java.io.Reader; |
+ |
+/** |
+ * A modified version of {@link FileCharSequenceProvider}, suited for reading from {@link File}s. |
+ */ |
+public class ExternalFileCharSequenceProvider { |
+ |
+ public static class FileCharSequenceException extends RuntimeException { |
+ private static final long serialVersionUID = 1L; |
+ |
+ /* package */FileCharSequenceException(CoreException e) { |
+ super(e); |
+ } |
+ |
+ /* package */FileCharSequenceException(IOException e) { |
+ super(e); |
+ } |
+ |
+ public void throwWrappedException() throws CoreException, IOException { |
+ Throwable wrapped = getCause(); |
+ if (wrapped instanceof CoreException) { |
+ throw (CoreException) wrapped; |
+ } else if (wrapped instanceof IOException) { |
+ throw (IOException) wrapped; |
+ } |
+ // not possible |
+ } |
+ } |
+ private static final class Buffer { |
+ private final char[] fBuf; |
+ private int fOffset; |
+ private int fLength; |
+ |
+ private Buffer fNext; |
+ private Buffer fPrevious; |
+ |
+ public Buffer() { |
+ fBuf = new char[BUFFER_SIZE]; |
+ reset(); |
+ fNext = this; |
+ fPrevious = this; |
+ } |
+ |
+ public StringBuffer append(StringBuffer buf, int start, int length) { |
+ return buf.append(fBuf, start - fOffset, length); |
+ } |
+ |
+ public StringBuffer appendAll(StringBuffer buf) { |
+ return buf.append(fBuf, 0, fLength); |
+ } |
+ |
+ public boolean contains(int pos) { |
+ int offset = fOffset; |
+ return offset <= pos && pos < offset + fLength; |
+ } |
+ |
+ /** |
+ * Fills the buffer by reading from the given reader. |
+ * |
+ * @param reader the reader to read from |
+ * @param pos the offset of the reader in the file |
+ * @return returns true if the end of the file has been reached |
+ * @throws IOException if reading from the buffer fails |
+ */ |
+ public boolean fill(Reader reader, int pos) throws IOException { |
+ int res = reader.read(fBuf); |
+ if (res == -1) { |
+ fOffset = pos; |
+ fLength = 0; |
+ return true; |
+ } |
+ |
+ int charsRead = res; |
+ while (charsRead < BUFFER_SIZE) { |
+ res = reader.read(fBuf, charsRead, BUFFER_SIZE - charsRead); |
+ if (res == -1) { |
+ fOffset = pos; |
+ fLength = charsRead; |
+ return true; |
+ } |
+ charsRead += res; |
+ } |
+ fOffset = pos; |
+ fLength = BUFFER_SIZE; |
+ return false; |
+ } |
+ |
+ public char get(int pos) { |
+ return fBuf[pos - fOffset]; |
+ } |
+ |
+ public int getEndOffset() { |
+ return fOffset + fLength; |
+ } |
+ |
+ public Buffer getNext() { |
+ return fNext; |
+ } |
+ |
+ public Buffer getPrevious() { |
+ return fPrevious; |
+ } |
+ |
+ public void insertBefore(Buffer other) { |
+ fNext = other; |
+ fPrevious = other.fPrevious; |
+ fPrevious.fNext = this; |
+ other.fPrevious = this; |
+ } |
+ |
+ public void removeFromChain() { |
+ fPrevious.fNext = fNext; |
+ fNext.fPrevious = fPrevious; |
+ |
+ fNext = this; |
+ fPrevious = this; |
+ } |
+ |
+ public void reset() { |
+ fOffset = -1; |
+ fLength = 0; |
+ } |
+ } |
+ |
+ private static final class CharSubSequence implements CharSequence { |
+ |
+ private final int fSequenceOffset; |
+ private final int fSequenceLength; |
+ private final FileCharSequence fParent; |
+ |
+ public CharSubSequence(FileCharSequence parent, int offset, int length) { |
+ fParent = parent; |
+ fSequenceOffset = offset; |
+ fSequenceLength = length; |
+ } |
+ |
+ @Override |
+ public char charAt(int index) { |
+ if (index < 0) { |
+ throw new IndexOutOfBoundsException("index must be larger than 0"); //$NON-NLS-1$ |
+ } |
+ if (index >= fSequenceLength) { |
+ throw new IndexOutOfBoundsException("index must be smaller than length"); //$NON-NLS-1$ |
+ } |
+ return fParent.charAt(fSequenceOffset + index); |
+ } |
+ |
+ @Override |
+ public int length() { |
+ return fSequenceLength; |
+ } |
+ |
+ @Override |
+ public CharSequence subSequence(int start, int end) { |
+ if (end < start) { |
+ throw new IndexOutOfBoundsException("end cannot be smaller than start"); //$NON-NLS-1$ |
+ } |
+ if (start < 0) { |
+ throw new IndexOutOfBoundsException("start must be larger than 0"); //$NON-NLS-1$ |
+ } |
+ if (end > fSequenceLength) { |
+ throw new IndexOutOfBoundsException("end must be smaller or equal than length"); //$NON-NLS-1$ |
+ } |
+ return fParent.subSequence(fSequenceOffset + start, fSequenceOffset + end); |
+ } |
+ |
+ @Override |
+ public String toString() { |
+ try { |
+ return fParent.getSubstring(fSequenceOffset, fSequenceLength); |
+ } catch (IOException e) { |
+ throw new FileCharSequenceException(e); |
+ } catch (CoreException e) { |
+ throw new FileCharSequenceException(e); |
+ } |
+ } |
+ } |
+ |
+ private final class FileCharSequence implements CharSequence { |
+ |
+ private static final String CHARSET_UTF_8 = "UTF-8"; //$NON-NLS-1$ |
+ |
+ private Reader fReader; |
+ private int fReaderPos; |
+ |
+ private Integer fLength; |
+ |
+ private Buffer fMostCurrentBuffer; // access to the buffer chain |
+ private int fNumberOfBuffers; |
+ |
+ private File fFile; |
+ |
+ public FileCharSequence(File file) throws CoreException, IOException { |
+ fNumberOfBuffers = 0; |
+ reset(file); |
+ } |
+ |
+ @Override |
+ public char charAt(final int index) { |
+ final Buffer current = fMostCurrentBuffer; |
+ if (current != null && current.contains(index)) { |
+ return current.get(index); |
+ } |
+ |
+ if (index < 0) { |
+ throw new IndexOutOfBoundsException("index must be larger than 0"); //$NON-NLS-1$ |
+ } |
+ if (fLength != null && index >= fLength.intValue()) { |
+ throw new IndexOutOfBoundsException("index must be smaller than length"); //$NON-NLS-1$ |
+ } |
+ |
+ try { |
+ final Buffer buffer = getBuffer(index); |
+ if (buffer == null) { |
+ throw new IndexOutOfBoundsException("index must be smaller than length"); //$NON-NLS-1$ |
+ } |
+ if (buffer != fMostCurrentBuffer) { |
+ // move to first |
+ if (buffer.getNext() != fMostCurrentBuffer) { // already before the current? |
+ buffer.removeFromChain(); |
+ buffer.insertBefore(fMostCurrentBuffer); |
+ } |
+ fMostCurrentBuffer = buffer; |
+ } |
+ return buffer.get(index); |
+ } catch (IOException e) { |
+ throw new FileCharSequenceException(e); |
+ } catch (CoreException e) { |
+ throw new FileCharSequenceException(e); |
+ } |
+ } |
+ |
+ public void close() throws IOException { |
+ clearReader(); |
+ } |
+ |
+ public String getSubstring(int start, int length) throws IOException, CoreException { |
+ int pos = start; |
+ int endPos = start + length; |
+ |
+ if (fLength != null && endPos > fLength.intValue()) { |
+ throw new IndexOutOfBoundsException("end must be smaller than length"); //$NON-NLS-1$ |
+ } |
+ |
+ StringBuffer res = new StringBuffer(length); |
+ |
+ Buffer buffer = getBuffer(pos); |
+ while (pos < endPos && buffer != null) { |
+ int bufEnd = buffer.getEndOffset(); |
+ if (bufEnd >= endPos) { |
+ return buffer.append(res, pos, endPos - pos).toString(); |
+ } |
+ buffer.append(res, pos, bufEnd - pos); |
+ pos = bufEnd; |
+ buffer = getBuffer(pos); |
+ } |
+ return res.toString(); |
+ } |
+ |
+ @Override |
+ public int length() { |
+ if (fLength == null) { |
+ try { |
+ getBuffer(Integer.MAX_VALUE); |
+ } catch (IOException e) { |
+ throw new FileCharSequenceException(e); |
+ } catch (CoreException e) { |
+ throw new FileCharSequenceException(e); |
+ } |
+ } |
+ return fLength.intValue(); |
+ } |
+ |
+ public void reset(File file) throws CoreException, IOException { |
+ fFile = file; |
+ fLength = null; // only calculated on demand |
+ |
+ Buffer curr = fMostCurrentBuffer; |
+ if (curr != null) { |
+ do { |
+ curr.reset(); |
+ curr = curr.getNext(); |
+ } while (curr != fMostCurrentBuffer); |
+ } |
+ initializeReader(); |
+ } |
+ |
+ @Override |
+ public CharSequence subSequence(int start, int end) { |
+ if (end < start) { |
+ throw new IndexOutOfBoundsException("end cannot be smaller than start"); //$NON-NLS-1$ |
+ } |
+ if (start < 0) { |
+ throw new IndexOutOfBoundsException("start must be larger than 0"); //$NON-NLS-1$ |
+ } |
+ if (fLength != null && end > fLength.intValue()) { |
+ throw new IndexOutOfBoundsException("end must be smaller than length"); //$NON-NLS-1$ |
+ } |
+ return new CharSubSequence(this, start, end - start); |
+ } |
+ |
+ @Override |
+ public String toString() { |
+ int len = fLength != null ? fLength.intValue() : 4000; |
+ StringBuffer res = new StringBuffer(len); |
+ try { |
+ Buffer buffer = getBuffer(0); |
+ while (buffer != null) { |
+ buffer.appendAll(res); |
+ buffer = getBuffer(res.length()); |
+ } |
+ return res.toString(); |
+ } catch (IOException e) { |
+ throw new FileCharSequenceException(e); |
+ } catch (CoreException e) { |
+ throw new FileCharSequenceException(e); |
+ } |
+ } |
+ |
+ private void clearReader() throws IOException { |
+ if (fReader != null) { |
+ fReader.close(); |
+ } |
+ fReader = null; |
+ fReaderPos = Integer.MAX_VALUE; |
+ } |
+ |
+ private boolean fillBuffer(Buffer buffer, int pos) throws CoreException, IOException { |
+ if (fReaderPos > pos) { |
+ initializeReader(); |
+ } |
+ |
+ do { |
+ boolean endReached = buffer.fill(fReader, fReaderPos); |
+ fReaderPos = buffer.getEndOffset(); |
+ if (endReached) { |
+ fLength = new Integer(fReaderPos); // at least we know the size of the file now |
+ fReaderPos = Integer.MAX_VALUE; // will have to reset next time |
+ return true; |
+ } |
+ } while (fReaderPos <= pos); |
+ |
+ return true; |
+ } |
+ |
+ private Buffer findBufferToUse() { |
+ if (fNumberOfBuffers < NUMBER_OF_BUFFERS) { |
+ fNumberOfBuffers++; |
+ Buffer newBuffer = new Buffer(); |
+ if (fMostCurrentBuffer == null) { |
+ fMostCurrentBuffer = newBuffer; |
+ return newBuffer; |
+ } |
+ newBuffer.insertBefore(fMostCurrentBuffer); // insert before first |
+ return newBuffer; |
+ } |
+ return fMostCurrentBuffer.getPrevious(); |
+ } |
+ |
+ private Buffer getBuffer(int pos) throws IOException, CoreException { |
+ Buffer curr = fMostCurrentBuffer; |
+ if (curr != null) { |
+ do { |
+ if (curr.contains(pos)) { |
+ return curr; |
+ } |
+ curr = curr.getNext(); |
+ } while (curr != fMostCurrentBuffer); |
+ } |
+ |
+ Buffer buf = findBufferToUse(); |
+ fillBuffer(buf, pos); |
+ if (buf.contains(pos)) { |
+ return buf; |
+ } |
+ return null; |
+ } |
+ |
+ private InputStream getInputStream(String charset) throws CoreException, IOException { |
+// boolean ok = false; |
+ InputStream contents = new FileInputStream(fFile); |
+ |
+//TODO(pquitslund): verify that we don't need this |
+// try { |
+// if (CHARSET_UTF_8.equals(charset)) { |
+// /* |
+// * This is a workaround for a corresponding bug in Java readers and writer, see |
+// * http://developer.java.sun.com/developer/bugParade/bugs/4508058.html we remove the BOM |
+// * before passing the stream to the reader |
+// */ |
+// IContentDescription description = fFile.getContentDescription(); |
+// if ((description != null) |
+// && (description.getProperty(IContentDescription.BYTE_ORDER_MARK) != null)) { |
+// int bomLength = IContentDescription.BOM_UTF_8.length; |
+// byte[] bomStore = new byte[bomLength]; |
+// int bytesRead = 0; |
+// do { |
+// int bytes = contents.read(bomStore, bytesRead, bomLength - bytesRead); |
+// if (bytes == -1) { |
+// throw new IOException(); |
+// } |
+// bytesRead += bytes; |
+// } while (bytesRead < bomLength); |
+// |
+// if (!Arrays.equals(bomStore, IContentDescription.BOM_UTF_8)) { |
+// // discard file reader, we were wrong, no BOM -> new stream |
+// contents.close(); |
+// contents = fFile.getContents(); |
+// } |
+// } |
+// } |
+// ok = true; |
+// } finally { |
+// if (!ok && contents != null) { |
+// try { |
+// contents.close(); |
+// } catch (IOException ex) { |
+// // ignore |
+// } |
+// } |
+// } |
+ return contents; |
+ } |
+ |
+ private void initializeReader() throws CoreException, IOException { |
+ if (fReader != null) { |
+ fReader.close(); |
+ } |
+// String charset = fFile.getCharset(); |
+ //TODO(pquitslund): is this safe? |
+ String charset = CHARSET_UTF_8; |
+ |
+ fReader = new InputStreamReader(getInputStream(charset), charset); |
+ fReaderPos = 0; |
+ } |
+ } |
+ |
+ private static int NUMBER_OF_BUFFERS = 3; |
+ |
+ public static int BUFFER_SIZE = 2 << 18; // public for testing |
+ |
+ private FileCharSequence fReused = null; |
+ |
+ public CharSequence newCharSequence(File file) throws CoreException, IOException { |
+ if (fReused == null) { |
+ return new FileCharSequence(file); |
+ } |
+ FileCharSequence curr = fReused; |
+ fReused = null; |
+ curr.reset(file); |
+ return curr; |
+ } |
+ |
+ public void releaseCharSequence(CharSequence seq) throws IOException { |
+ if (seq instanceof FileCharSequence) { |
+ FileCharSequence curr = (FileCharSequence) seq; |
+ try { |
+ curr.close(); |
+ } finally { |
+ if (fReused == null) { |
+ fReused = curr; |
+ } |
+ } |
+ } |
+ } |
+ |
+} |