| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2017 Eric Lafortune @ GuardSquare |
| * |
| * 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 2 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, write to the Free Software Foundation, Inc., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| package proguard; |
| |
| import java.io.*; |
| |
| |
| /** |
| * An abstract reader of words, with the possibility to include other readers. |
| * Words are separated by spaces or broken off at delimiters. Words containing |
| * spaces or delimiters can be quoted with single or double quotes. |
| * Comments (everything starting with '#' on a single line) are ignored. |
| * |
| * @author Eric Lafortune |
| * @noinspection TailRecursion |
| */ |
| public abstract class WordReader |
| { |
| private static final char COMMENT_CHARACTER = '#'; |
| |
| |
| private File baseDir; |
| private WordReader includeWordReader; |
| private String currentLine; |
| private int currentLineLength; |
| private int currentIndex; |
| private String currentWord; |
| private String currentComments; |
| |
| |
| /** |
| * Creates a new WordReader with the given base directory. |
| */ |
| protected WordReader(File baseDir) |
| { |
| this.baseDir = baseDir; |
| } |
| |
| |
| /** |
| * Sets the base directory of this reader. |
| */ |
| public void setBaseDir(File baseDir) |
| { |
| if (includeWordReader != null) |
| { |
| includeWordReader.setBaseDir(baseDir); |
| } |
| else |
| { |
| this.baseDir = baseDir; |
| } |
| } |
| |
| |
| /** |
| * Returns the base directory of this reader, if any. |
| */ |
| public File getBaseDir() |
| { |
| return includeWordReader != null ? |
| includeWordReader.getBaseDir() : |
| baseDir; |
| } |
| |
| |
| /** |
| * Specifies to start reading words from the given WordReader. When it is |
| * exhausted, this WordReader will continue to provide its own words. |
| * |
| * @param newIncludeWordReader the WordReader that will start reading words. |
| */ |
| public void includeWordReader(WordReader newIncludeWordReader) |
| { |
| if (includeWordReader == null) |
| { |
| includeWordReader = newIncludeWordReader; |
| } |
| else |
| { |
| includeWordReader.includeWordReader(newIncludeWordReader); |
| } |
| } |
| |
| |
| /** |
| * Reads a word from this WordReader, or from one of its active included |
| * WordReader objects. |
| * |
| * @param isFileName return a complete line (or argument), if the word |
| * isn't an option (it doesn't start with '-'). |
| * @return the read word. |
| */ |
| public String nextWord(boolean isFileName) throws IOException |
| { |
| currentWord = null; |
| |
| // See if we have an included reader to produce a word. |
| if (includeWordReader != null) |
| { |
| // Does the included word reader still produce a word? |
| currentWord = includeWordReader.nextWord(isFileName); |
| if (currentWord != null) |
| { |
| // Return it if so. |
| return currentWord; |
| } |
| |
| // Otherwise close and ditch the word reader. |
| includeWordReader.close(); |
| includeWordReader = null; |
| } |
| |
| // Get a word from this reader. |
| |
| // Skip any whitespace and comments left on the current line. |
| if (currentLine != null) |
| { |
| // Skip any leading whitespace. |
| while (currentIndex < currentLineLength && |
| Character.isWhitespace(currentLine.charAt(currentIndex))) |
| { |
| currentIndex++; |
| } |
| |
| // Skip any comments. |
| if (currentIndex < currentLineLength && |
| isComment(currentLine.charAt(currentIndex))) |
| { |
| currentIndex = currentLineLength; |
| } |
| } |
| |
| // Make sure we have a non-blank line. |
| while (currentLine == null || currentIndex == currentLineLength) |
| { |
| currentLine = nextLine(); |
| if (currentLine == null) |
| { |
| return null; |
| } |
| |
| currentLineLength = currentLine.length(); |
| |
| // Skip any leading whitespace. |
| currentIndex = 0; |
| while (currentIndex < currentLineLength && |
| Character.isWhitespace(currentLine.charAt(currentIndex))) |
| { |
| currentIndex++; |
| } |
| |
| // Remember any leading comments. |
| if (currentIndex < currentLineLength && |
| isComment(currentLine.charAt(currentIndex))) |
| { |
| // Remember the comments. |
| String comment = currentLine.substring(currentIndex + 1); |
| currentComments = currentComments == null ? |
| comment : |
| currentComments + '\n' + comment; |
| |
| // Skip the comments. |
| currentIndex = currentLineLength; |
| } |
| } |
| |
| // Find the word starting at the current index. |
| int startIndex = currentIndex; |
| int endIndex; |
| |
| char startChar = currentLine.charAt(startIndex); |
| |
| if (isQuote(startChar)) |
| { |
| // The next word is starting with a quote character. |
| // Skip the opening quote. |
| startIndex++; |
| |
| // The next word is a quoted character string. |
| // Find the closing quote. |
| do |
| { |
| currentIndex++; |
| |
| if (currentIndex == currentLineLength) |
| { |
| currentWord = currentLine.substring(startIndex-1, currentIndex); |
| throw new IOException("Missing closing quote for "+locationDescription()); |
| } |
| } |
| while (currentLine.charAt(currentIndex) != startChar); |
| |
| endIndex = currentIndex++; |
| } |
| else if (isFileName && |
| !isOption(startChar)) |
| { |
| // The next word is a (possibly optional) file name. |
| // Find the end of the line, the first path separator, the first |
| // option, or the first comment. |
| while (currentIndex < currentLineLength) |
| { |
| char currentCharacter = currentLine.charAt(currentIndex); |
| if (isFileDelimiter(currentCharacter) || |
| ((isOption(currentCharacter) || |
| isComment(currentCharacter)) && |
| Character.isWhitespace(currentLine.charAt(currentIndex-1)))) { |
| break; |
| } |
| |
| currentIndex++; |
| } |
| |
| endIndex = currentIndex; |
| |
| // Trim any trailing whitespace. |
| while (endIndex > startIndex && |
| Character.isWhitespace(currentLine.charAt(endIndex-1))) |
| { |
| endIndex--; |
| } |
| } |
| else if (isDelimiter(startChar)) |
| { |
| // The next word is a single delimiting character. |
| endIndex = ++currentIndex; |
| } |
| else |
| { |
| // The next word is a simple character string. |
| // Find the end of the line, the first delimiter, or the first |
| // white space. |
| while (currentIndex < currentLineLength) |
| { |
| char currentCharacter = currentLine.charAt(currentIndex); |
| if (isDelimiter(currentCharacter) || |
| Character.isWhitespace(currentCharacter) || |
| isComment(currentCharacter)) { |
| break; |
| } |
| |
| currentIndex++; |
| } |
| |
| endIndex = currentIndex; |
| } |
| |
| // Remember and return the parsed word. |
| currentWord = currentLine.substring(startIndex, endIndex); |
| |
| return currentWord; |
| } |
| |
| |
| /** |
| * Returns the comments collected before returning the last word. |
| * Starts collecting new comments. |
| * |
| * @return the collected comments, or <code>null</code> if there weren't any. |
| */ |
| public String lastComments() throws IOException |
| { |
| if (includeWordReader == null) |
| { |
| String comments = currentComments; |
| currentComments = null; |
| return comments; |
| } |
| else |
| { |
| return includeWordReader.lastComments(); |
| } |
| } |
| |
| |
| /** |
| * Constructs a readable description of the current position in this |
| * WordReader and its included WordReader objects. |
| * |
| * @return the description. |
| */ |
| public String locationDescription() |
| { |
| return |
| (includeWordReader == null ? |
| (currentWord == null ? |
| "end of " : |
| "'" + currentWord + "' in " ) : |
| (includeWordReader.locationDescription() + ",\n" + |
| " included from ")) + |
| lineLocationDescription(); |
| } |
| |
| |
| /** |
| * Reads a line from this WordReader, or from one of its active included |
| * WordReader objects. |
| * |
| * @return the read line. |
| */ |
| protected abstract String nextLine() throws IOException; |
| |
| |
| /** |
| * Returns a readable description of the current WordReader position. |
| * |
| * @return the description. |
| */ |
| protected abstract String lineLocationDescription(); |
| |
| |
| /** |
| * Closes the FileWordReader. |
| */ |
| public void close() throws IOException |
| { |
| // Close and ditch the included word reader, if any. |
| if (includeWordReader != null) |
| { |
| includeWordReader.close(); |
| includeWordReader = null; |
| } |
| } |
| |
| |
| // Small utility methods. |
| |
| private boolean isOption(char character) |
| { |
| return character == '-'; |
| } |
| |
| |
| private boolean isComment(char character) |
| { |
| return character == COMMENT_CHARACTER; |
| } |
| |
| |
| private boolean isDelimiter(char character) |
| { |
| return character == '@' || |
| character == '{' || |
| character == '}' || |
| character == '(' || |
| character == ')' || |
| character == ',' || |
| character == ';' || |
| character == File.pathSeparatorChar; |
| } |
| |
| |
| private boolean isFileDelimiter(char character) |
| { |
| return character == '(' || |
| character == ')' || |
| character == ',' || |
| character == ';' || |
| character == File.pathSeparatorChar; |
| } |
| |
| |
| private boolean isQuote(char character) |
| { |
| return character == '\'' || |
| character == '"'; |
| } |
| } |