/** * Copyright (C) 2005 - 2014 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.util.file; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import org.apache.commons.vfs.FileObject; import org.apache.commons.vfs.FileSystemManager; import org.apache.commons.vfs.VFS; import org.eclim.Services; import org.eclim.util.IOUtils; /** * Compiles a list of char offsets to line numbers and stores them for quick * translation of offset to line number and column. * * @author Eric Van Dewoestine */ public class FileOffsets { private Integer[] offsets; private String[] multiByteLines; private FileOffsets () { } /** * Reads the supplied file and compiles a list of offsets. * * @param filename The file to compile a list of offsets for. * @return The FileOffsets instance. */ public static FileOffsets compile(String filename) { try{ FileSystemManager fsManager = VFS.getManager(); FileObject file = fsManager.resolveFile(filename.replace("%", "%25")); // disable caching (the cache seems to become invalid at some point // causing vfs errors). //fsManager.getFilesCache().clear(file.getFileSystem()); if(!file.exists()){ throw new IllegalArgumentException( Services.getMessage("file.not.found", filename)); } return compile(file.getContent().getInputStream()); }catch(Exception e){ throw new RuntimeException(e); } } /** * Reads the supplied input stream and compiles a list of offsets. * * @param in The InputStream to compile a list of offsets for. * @return The FileOffsets instance. */ public static FileOffsets compile(InputStream in) { FileOffsets offsets = new FileOffsets(); offsets.compileOffsets(in); return offsets; } /** * Reads the supplied input stream and compiles a list of offsets. * * @param in The InputStream to compile a list of offsets for. */ private void compileOffsets(InputStream in) { BufferedReader reader = null; try{ reader = new BufferedReader(new InputStreamReader(in)); ArrayList<Integer> lines = new ArrayList<Integer>(); lines.add(new Integer(0)); ArrayList<String> byteLines = new ArrayList<String>(); byteLines.add(null); int offset = 0; String line = null; while((line = reader.readLine()) != null){ offset += line.length(); lines.add(new Integer(offset)); if (line.length() != line.getBytes().length){ byteLines.add(line); }else{ byteLines.add(null); } } offsets = (Integer[])lines.toArray(new Integer[lines.size()]); multiByteLines = (String[])byteLines.toArray(new String[byteLines.size()]); }catch(Exception e){ throw new RuntimeException(e); }finally{ IOUtils.closeQuietly(reader); } } /** * Converts the supplied offset into an int array where the first element is * the line number and the second is the column number. * * @param offset The offset. * @return The line and column int array. */ public int[] offsetToLineColumn(int offset) { if(offset <= 0){ return new int[]{1, 1}; } int bot = -1; int top = offsets.length - 1; while (top - bot > 1) { int mid = (top + bot) / 2; if (offsets[mid].intValue() < offset){ bot = mid; }else{ top = mid; } } if(offsets[top].intValue() > offset){ top--; } int line = top + 1; int column = 1 + offset - offsets[top].intValue(); String value = multiByteLines.length > line ? multiByteLines[line] : null; if (value != null){ column = value.substring(0, column).getBytes().length; } return new int[]{line, column}; } /** * Gets the offset where the supplied line starts. * * @param line The line. * @return The starting offset. */ public int getLineStart(int line) { return offsets[line - 1].intValue(); } /** * Gets the offset where the supplied line ends. * * @param line The line. * @return The ending offset. */ public int getLineEnd(int line) { if (offsets.length == line){ return offsets[offsets.length - 1].intValue(); } return offsets[line].intValue() - 1; } }