/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package com.aptana.ide.search.epl.internal.filesystem.text; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.Charset; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; /** * */ public class FileCharSequenceProvider { private static int NUMBER_OF_BUFFERS = 3; /** * */ public static int BUFFER_SIZE = 2 << 18; // public for testing private FileCharSequence fReused = null; /** * @param file * @return * @throws CoreException * @throws IOException */ public CharSequence newCharSequence(File file) throws CoreException, IOException { if (this.fReused == null) { return new FileCharSequence(file); } FileCharSequence curr = this.fReused; this.fReused = null; curr.reset(file); return curr; } /** * @param seq * @throws CoreException * @throws IOException */ public void releaseCharSequence(CharSequence seq) throws CoreException, IOException { if (seq instanceof FileCharSequence) { FileCharSequence curr = (FileCharSequence) seq; try { curr.close(); } finally { if (this.fReused == null) { this.fReused = curr; } } } } /** * @author Pavel Petrochenko * */ public static class FileCharSequenceException extends RuntimeException { private static final long serialVersionUID = 1L; /* package */FileCharSequenceException(IOException e) { super(e); } /* package */FileCharSequenceException(CoreException e) { super(e); } /** * @throws CoreException * @throws IOException */ public void throwWrappedException() throws CoreException, IOException { Throwable wrapped = this.getCause(); if (wrapped instanceof CoreException) { throw (CoreException) wrapped; } else if (wrapped instanceof IOException) { throw (IOException) wrapped; } // not possible } } /** * * @author Pavel Petrochenko * */ private static final class CharSubSequence implements CharSequence { private final int fSequenceOffset; private final int fSequenceLength; private final FileCharSequence fParent; /** * @param parent * @param offset * @param length */ public CharSubSequence(FileCharSequence parent, int offset, int length) { this.fParent = parent; this.fSequenceOffset = offset; this.fSequenceLength = length; } /* * (non-Javadoc) * * @see java.lang.CharSequence#length() */ /** * @see java.lang.CharSequence#length() */ public int length() { return this.fSequenceLength; } /* * (non-Javadoc) * * @see java.lang.CharSequence#charAt(int) */ /** * @see java.lang.CharSequence#charAt(int) */ public char charAt(int index) { if (index < 0) { throw new IndexOutOfBoundsException("index must be larger than 0"); //$NON-NLS-1$ } if (index >= this.fSequenceLength) { throw new IndexOutOfBoundsException("index must be smaller than length"); //$NON-NLS-1$ } return this.fParent.charAt(this.fSequenceOffset + index); } /* * (non-Javadoc) * * @see java.lang.CharSequence#subSequence(int, int) */ /** * @see java.lang.CharSequence#subSequence(int, int) */ 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 > this.fSequenceLength) { throw new IndexOutOfBoundsException("end must be smaller or equal than length"); //$NON-NLS-1$ } return this.fParent.subSequence(this.fSequenceOffset + start, this.fSequenceOffset + end); } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ /** * @see java.lang.Object#toString() */ public String toString() { try { return this.fParent.getSubstring(this.fSequenceOffset, this.fSequenceLength); } catch (IOException e) { throw new FileCharSequenceException(e); } catch (CoreException e) { throw new FileCharSequenceException(e); } } } /** * * @author Pavel Petrochenko * */ private static final class Buffer { private final char[] fBuf; private int fOffset; private int fLength; private Buffer fNext; private Buffer fPrevious; /** * */ public Buffer() { this.fBuf = new char[FileCharSequenceProvider.BUFFER_SIZE]; this.reset(); this.fNext = this; this.fPrevious = this; } /** * @param pos * @return */ public boolean contains(int pos) { int offset = this.fOffset; return (offset <= pos) && (pos < offset + this.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 */ public boolean fill(Reader reader, int pos) throws IOException { int res = reader.read(this.fBuf); if (res == -1) { this.fOffset = pos; this.fLength = 0; return true; } int charsRead = res; while (charsRead < FileCharSequenceProvider.BUFFER_SIZE) { res = reader.read(this.fBuf, charsRead, FileCharSequenceProvider.BUFFER_SIZE - charsRead); if (res == -1) { this.fOffset = pos; this.fLength = charsRead; return true; } charsRead += res; } this.fOffset = pos; this.fLength = FileCharSequenceProvider.BUFFER_SIZE; return false; } /** * @param pos * @return */ public char get(int pos) { return this.fBuf[pos - this.fOffset]; } /** * @param buf * @param start * @param length * @return */ public StringBuffer append(StringBuffer buf, int start, int length) { return buf.append(this.fBuf, start - this.fOffset, length); } /** * @param buf * @return buf */ public StringBuffer appendAll(StringBuffer buf) { return buf.append(this.fBuf, 0, this.fLength); } /** * @return of */ public int getEndOffset() { return this.fOffset + this.fLength; } /** * */ public void removeFromChain() { this.fPrevious.fNext = this.fNext; this.fNext.fPrevious = this.fPrevious; this.fNext = this; this.fPrevious = this; } /** * @param other */ public void insertBefore(Buffer other) { this.fNext = other; this.fPrevious = other.fPrevious; this.fPrevious.fNext = this; other.fPrevious = this; } /** * @return */ public Buffer getNext() { return this.fNext; } /** * @return prev */ public Buffer getPrevious() { return this.fPrevious; } /** * */ public void reset() { this.fOffset = -1; this.fLength = 0; } } /** * * @author Pavel Petrochenko * */ private final class FileCharSequence implements CharSequence { private Reader fReader; private int fReaderPos; private Integer fLength; private Buffer fMostCurrentBuffer; // access to the buffer chain private int fNumberOfBuffers; private File fFile; /** * @param file * @throws CoreException * @throws IOException */ public FileCharSequence(File file) throws CoreException, IOException { this.fNumberOfBuffers = 0; this.reset(file); } /** * @param file * @throws CoreException * @throws IOException */ public void reset(File file) throws CoreException, IOException { this.fFile = file; this.fLength = null; // only calculated on demand Buffer curr = this.fMostCurrentBuffer; if (curr != null) { do { curr.reset(); curr = curr.getNext(); } while (curr != this.fMostCurrentBuffer); } this.initializeReader(); } private void initializeReader() throws CoreException, IOException { if (this.fReader != null) { this.fReader.close(); } String charset = ResourcesPlugin.getEncoding(); this.fReader = new InputStreamReader(this.getInputStream(charset), charset); this.fReaderPos = 0; } private InputStream getInputStream(String charset) throws CoreException, IOException { InputStream contents = new BufferedInputStream(new FileInputStream(this.fFile)); // 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 clearReader() throws IOException { if (this.fReader != null) { this.fReader.close(); } this.fReader = null; this.fReaderPos = Integer.MAX_VALUE; } /** * @see java.lang.CharSequence#length() */ public int length() { if (this.fLength == null) { try { this.getBuffer(Integer.MAX_VALUE); } catch (IOException e) { throw new FileCharSequenceException(e); } catch (CoreException e) { throw new FileCharSequenceException(e); } } return this.fLength.intValue(); } private Buffer getBuffer(int pos) throws IOException, CoreException { Buffer curr = this.fMostCurrentBuffer; if (curr != null) { do { if (curr.contains(pos)) { return curr; } curr = curr.getNext(); } while (curr != this.fMostCurrentBuffer); } Buffer buf = this.findBufferToUse(); this.fillBuffer(buf, pos); if (buf.contains(pos)) { return buf; } return null; } private Buffer findBufferToUse() { if (this.fNumberOfBuffers < FileCharSequenceProvider.NUMBER_OF_BUFFERS) { this.fNumberOfBuffers++; Buffer newBuffer = new Buffer(); if (this.fMostCurrentBuffer == null) { this.fMostCurrentBuffer = newBuffer; return newBuffer; } newBuffer.insertBefore(this.fMostCurrentBuffer); // insert before first return newBuffer; } return this.fMostCurrentBuffer.getPrevious(); } private boolean fillBuffer(Buffer buffer, int pos) throws CoreException, IOException { if (this.fReaderPos > pos) { this.initializeReader(); } do { boolean endReached = buffer.fill(this.fReader, this.fReaderPos); this.fReaderPos = buffer.getEndOffset(); if (endReached) { this.fLength = new Integer(this.fReaderPos); // at least we know the size of the file now this.fReaderPos = Integer.MAX_VALUE; // will have to reset next time return true; } } while (this.fReaderPos <= pos); return true; } /** * @see java.lang.CharSequence#charAt(int) */ public char charAt(final int index) { final Buffer current = this.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 ((this.fLength != null) && (index >= this.fLength.intValue())) { throw new IndexOutOfBoundsException("index must be smaller than length"); //$NON-NLS-1$ } try { final Buffer buffer = this.getBuffer(index); if (buffer == null) { throw new IndexOutOfBoundsException("index must be smaller than length"); //$NON-NLS-1$ } if (buffer != this.fMostCurrentBuffer) { // move to first if (buffer.getNext() != this.fMostCurrentBuffer) { // already before the current? buffer.removeFromChain(); buffer.insertBefore(this.fMostCurrentBuffer); } this.fMostCurrentBuffer = buffer; } return buffer.get(index); } catch (IOException e) { throw new FileCharSequenceException(e); } catch (CoreException e) { throw new FileCharSequenceException(e); } } /** * @param start * @param length * @return string * @throws IOException * @throws CoreException */ public String getSubstring(int start, int length) throws IOException, CoreException { int pos = start; int endPos = start + length; if ((this.fLength != null) && (endPos > this.fLength.intValue())) { throw new IndexOutOfBoundsException("end must be smaller than length"); //$NON-NLS-1$ } StringBuffer res = new StringBuffer(length); Buffer buffer = this.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 = this.getBuffer(pos); } return res.toString(); } /** * @see java.lang.CharSequence#subSequence(int, int) */ 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 ((this.fLength != null) && (end > this.fLength.intValue())) { throw new IndexOutOfBoundsException("end must be smaller than length"); //$NON-NLS-1$ } return new CharSubSequence(this, start, end - start); } /** * @throws IOException */ public void close() throws IOException { this.clearReader(); } /** * @see java.lang.Object#toString() */ public String toString() { int len = this.fLength != null ? this.fLength.intValue() : 4000; StringBuffer res = new StringBuffer(len); try { Buffer buffer = this.getBuffer(0); while (buffer != null) { buffer.appendAll(res); buffer = this.getBuffer(res.length()); } return res.toString(); } catch (IOException e) { throw new FileCharSequenceException(e); } catch (CoreException e) { throw new FileCharSequenceException(e); } } } }