blob: 110fce3b3c44e637126d65cd6cbbafc3e40eb074 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2013 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 == '"';
}
}