| /* | |
| * 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 == '"'; | |
| } | |
| } |