| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.android.ide.common.blame.parser.aapt; |
| |
| import com.android.annotations.NonNull; |
| import com.google.common.base.Charsets; |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Lists; |
| import com.google.common.io.Files; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * A read-only representation of the text of a file. |
| */ |
| class ReadOnlyDocument { |
| |
| @NonNull |
| private final String mFileContents; |
| |
| @NonNull |
| private final List<Integer> myOffsets; |
| |
| private File myFile; |
| |
| private long myLastModified; |
| |
| /** |
| * Creates a new {@link ReadOnlyDocument} for the given file. |
| * |
| * @param file the file whose text will be stored in the document. UTF-8 charset is used to |
| * decode the contents of the file. |
| * @throws java.io.IOException if an error occurs while reading the file. |
| */ |
| @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") |
| ReadOnlyDocument(@NonNull File file) throws IOException { |
| String xml = Files.toString(file, Charsets.UTF_8); |
| if (xml.startsWith("\uFEFF")) { // Strip byte order mark if necessary |
| xml = xml.substring(1); |
| } |
| mFileContents = xml; |
| myFile = file; |
| myLastModified = file.lastModified(); |
| myOffsets = Lists.newArrayListWithExpectedSize(mFileContents.length() / 30); |
| myOffsets.add(0); |
| for (int i = 0; i < mFileContents.length(); i++) { |
| char c = mFileContents.charAt(i); |
| if (c == '\n') { |
| myOffsets.add(i + 1); |
| } |
| } |
| } |
| |
| /** |
| * Returns true if the document contents are stale (e.g. no longer current) |
| */ |
| public boolean isStale() { |
| long now = myFile.lastModified(); |
| return now == 0L || myLastModified < now; |
| } |
| |
| /** |
| * Returns the offset of the given line number, relative to the beginning of the document. |
| * |
| * @param lineNumber the given line number. |
| * @return the offset of the given line. -1 is returned if the document is empty, or if the |
| * given line number is negative or greater than the number of lines in the document. |
| */ |
| int lineOffset(int lineNumber) { |
| int index = lineNumber - 1; |
| if (index < 0 || index >= myOffsets.size()) { |
| return -1; |
| } |
| return myOffsets.get(index); |
| } |
| |
| /** |
| * Returns the line number of the given offset. |
| * |
| * @param offset the given offset. |
| * @return the line number of the given offset. -1 is returned if the document is empty or if |
| * the offset is greater than the position of the last character in the document. |
| */ |
| int lineNumber(int offset) { |
| for (int i = 0; i < myOffsets.size(); i++) { |
| int savedOffset = myOffsets.get(i); |
| if (offset <= savedOffset) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Finds the given text in the document, starting from the given offset. |
| * |
| * @param needle the text to find. |
| * @param offset the starting point of the search. |
| * @return the offset of the found result, or -1 if no match was found. |
| */ |
| int findText(@NonNull String needle, int offset) { |
| Preconditions.checkPositionIndex(offset, mFileContents.length()); |
| return mFileContents.indexOf(needle, offset); |
| } |
| |
| int findTextBackwards(String needle, int offset) { |
| Preconditions.checkPositionIndex(offset, mFileContents.length()); |
| return mFileContents.lastIndexOf(needle, offset); |
| } |
| |
| /** |
| * Returns the character at the given offset. |
| * |
| * @param offset the position, relative to the beginning of the document, of the character to |
| * return. |
| * @return the character at the given offset. |
| * @throws IndexOutOfBoundsException if the {@code offset} argument is negative or not less than |
| * the document's size. |
| */ |
| char charAt(int offset) { |
| return mFileContents.charAt(offset); |
| } |
| |
| /** |
| * Returns the sub sequence for the given range. |
| * |
| * @param start the starting offset. |
| * @param end the ending offset, or -1 for the end of the file. |
| * @return the sub sequence. |
| */ |
| String subsequence(int start, int end) { |
| return mFileContents.substring(start, end == -1 ? mFileContents.length() : end); |
| } |
| |
| /** |
| * Returns the contents of the document |
| * |
| * @return the contents |
| */ |
| String getContents() { |
| return mFileContents; |
| } |
| |
| /** |
| * @return the size (or length) of the document. |
| */ |
| int length() { |
| return mFileContents.length(); |
| } |
| } |