/******************************************************************************* * Copyright (c) 2010 Wind River Systems, Inc. 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: * Markus Schorn - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.internal.core.parser.scanner; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import org.eclipse.cdt.core.CCorePlugin; /** * Implementation of char array for a file referencing content via * soft references. */ public class FileCharArray extends LazyCharArray { private static final String UTF8_CHARSET_NAME = "UTF-8"; //$NON-NLS-1$ public static AbstractCharArray create(String fileName, String charSet, InputStream in) throws IOException { // no support for non-local files if (!(in instanceof FileInputStream)) { return null; } FileInputStream fis= (FileInputStream) in; if (!Charset.isSupported(charSet)) { charSet= System.getProperty("file.encoding"); //$NON-NLS-1$ } FileChannel channel = fis.getChannel(); final long lsize = channel.size(); if (lsize < CHUNK_SIZE) { return decodeSmallFile(channel, (int) lsize, charSet); } return new FileCharArray(fileName, charSet); } private static AbstractCharArray decodeSmallFile(FileChannel channel, int lsize, String charSet) throws IOException { ByteBuffer byteBuffer = ByteBuffer.allocate(lsize); channel.read(byteBuffer); byteBuffer.flip(); skipUTF8ByteOrderMark(byteBuffer, charSet); CharBuffer charBuffer = Charset.forName(charSet).decode(byteBuffer); char[] buf= extractChars(charBuffer); return new CharArray(buf); } private static void skipUTF8ByteOrderMark(ByteBuffer buf, String charset) { if (charset.equals(UTF8_CHARSET_NAME) && buf.remaining() >= 3) { int pos = buf.position(); if (buf.get(pos) == (byte) 0xEF && buf.get(++pos) == (byte) 0xBB && buf.get(++pos) == (byte) 0xBF) { buf.position(++pos); } } } private static char[] extractChars(CharBuffer charBuffer) { if (charBuffer.hasArray() && charBuffer.arrayOffset() == 0) { char[] buf = charBuffer.array(); if (buf.length == charBuffer.remaining()) return buf; } char[] buf = new char[charBuffer.remaining()]; charBuffer.get(buf); return buf; } private String fFileName; private String fCharSet; private FileChannel fChannel; private long fNextFileOffset= 0; private int fNextCharOffset= 0; private boolean fReachedEOF= false; private FileCharArray(String fileName, String charSet) { fFileName= fileName; fCharSet= charSet; } @Override protected Chunk createChunk(int chunkNumber) { FileInputStream fis; try { fis = new FileInputStream(fFileName); } catch (FileNotFoundException e1) { // File has been deleted in the meantime return null; } fChannel= fis.getChannel(); try { return super.createChunk(chunkNumber); } finally { fChannel= null; try { fis.close(); } catch (IOException e) { } } } @Override protected Chunk nextChunk() { if (fReachedEOF) return null; try { assert fChannel != null; final Charset charset = Charset.forName(fCharSet); final CharsetDecoder decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); int needBytes = 3 + (int) (CHUNK_SIZE * (double) decoder.averageCharsPerByte()); // avoid rounding errors. final ByteBuffer in = ByteBuffer.allocate(needBytes); final CharBuffer dest= CharBuffer.allocate(CHUNK_SIZE); boolean eof; CoderResult result; long fileOffset= fNextFileOffset; do { in.clear(); fChannel.position(fileOffset); fChannel.read(in); eof= in.remaining() > 0; in.flip(); if (fileOffset == 0) { skipUTF8ByteOrderMark(in, fCharSet); } result = decoder.decode(in, dest, eof); fileOffset+= in.position(); } while (result == CoderResult.UNDERFLOW && !eof); dest.flip(); if (dest.remaining() == 0) { fReachedEOF= true; return null; } if (eof && result == CoderResult.UNDERFLOW) { fReachedEOF= true; } final char[] chars = extractChars(dest); Chunk chunk = newChunk(fNextFileOffset, fileOffset, fNextCharOffset, chars); fNextFileOffset= fileOffset; fNextCharOffset+= chars.length; return chunk; } catch (Exception e) { // The file cannot be read CCorePlugin.log(e); fReachedEOF= true; return null; } } @Override protected void rereadChunkData(Chunk chunk, char[] dest) { FileInputStream fis; try { fis = new FileInputStream(fFileName); } catch (FileNotFoundException e1) { // File has been deleted in the meantime return; } try { FileChannel channel = fis.getChannel(); decode(channel, chunk.fSourceOffset, chunk.fSourceEndOffset, CharBuffer.wrap(dest)); } catch (IOException e) { // File cannot be read } finally { try { fis.close(); } catch (IOException e) { } } } private void decode(FileChannel channel, long fileOffset, long fileEndOffset, CharBuffer dest) throws IOException { final Charset charset = Charset.forName(fCharSet); final CharsetDecoder decoder = charset.newDecoder().onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); final ByteBuffer in = ByteBuffer.allocate((int) (fileEndOffset - fileOffset)); in.clear(); channel.position(fileOffset); channel.read(in); in.flip(); if (fileOffset == 0) { skipUTF8ByteOrderMark(in, fCharSet); } decoder.decode(in, dest, true); } }