/*
|
* ProGuard -- shrinking, optimization, obfuscation, and preverification
|
* of Java bytecode.
|
*
|
* Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu)
|
*
|
* 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 == '"';
|
}
|
}
|