WordReader.java revision db267bc191f906f55eaef21a27110cce2ec57fdf
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard;
22
23import java.io.*;
24
25
26/**
27 * An abstract reader of words, with the possibility to include other readers.
28 * Words are separated by spaces or broken off at delimiters. Words containing
29 * spaces or delimiters can be quoted with single or double quotes.
30 * Comments (everything starting with '#' on a single line) are ignored.
31 *
32 * @author Eric Lafortune
33 * @noinspection TailRecursion
34 */
35public abstract class WordReader
36{
37    private static final char COMMENT_CHARACTER = '#';
38
39
40    private File       baseDir;
41    private WordReader includeWordReader;
42    private String     currentLine;
43    private int        currentLineLength;
44    private int        currentIndex;
45    private String     currentWord;
46    private String     currentComments;
47
48
49    /**
50     * Creates a new WordReader with the given base directory.
51     */
52    protected WordReader(File baseDir)
53    {
54        this.baseDir = baseDir;
55    }
56
57
58    /**
59     * Sets the base directory of this reader.
60     */
61    public void setBaseDir(File baseDir)
62    {
63        if (includeWordReader != null)
64        {
65            includeWordReader.setBaseDir(baseDir);
66        }
67        else
68        {
69            this.baseDir = baseDir;
70        }
71    }
72
73
74    /**
75     * Returns the base directory of this reader, if any.
76     */
77    public File getBaseDir()
78    {
79        return includeWordReader != null ?
80            includeWordReader.getBaseDir() :
81            baseDir;
82    }
83
84
85    /**
86     * Specifies to start reading words from the given WordReader. When it is
87     * exhausted, this WordReader will continue to provide its own words.
88     *
89     * @param newIncludeWordReader the WordReader that will start reading words.
90     */
91    public void includeWordReader(WordReader newIncludeWordReader)
92    {
93        if (includeWordReader == null)
94        {
95            includeWordReader = newIncludeWordReader;
96        }
97        else
98        {
99            includeWordReader.includeWordReader(newIncludeWordReader);
100        }
101    }
102
103
104    /**
105     * Reads a word from this WordReader, or from one of its active included
106     * WordReader objects.
107     *
108     * @return the read word.
109     */
110    public String nextWord() throws IOException
111    {
112        currentWord = null;
113
114        // See if we have an included reader to produce a word.
115        if (includeWordReader != null)
116        {
117            // Does the included word reader still produce a word?
118            currentWord = includeWordReader.nextWord();
119            if (currentWord != null)
120            {
121                // Return it if so.
122                return currentWord;
123            }
124
125            // Otherwise close and ditch the word reader.
126            includeWordReader.close();
127            includeWordReader = null;
128        }
129
130        // Get a word from this reader.
131
132        // Skip leading whitespace.
133        while (currentLine != null &&
134               currentIndex < currentLineLength &&
135               Character.isWhitespace(currentLine.charAt(currentIndex)))
136        {
137            currentIndex++;
138        }
139
140        // Make sure we have a non-blank line.
141        while (currentLine == null || currentIndex == currentLineLength)
142        {
143            currentLine = nextLine();
144            if (currentLine == null)
145            {
146                return null;
147            }
148
149            // Trim off any comments.
150            int comments_start = currentLine.indexOf(COMMENT_CHARACTER);
151            if (comments_start >= 0)
152            {
153                currentLineLength = comments_start;
154
155                // Remember the comments.
156                String comment = currentLine.substring(comments_start + 1);
157                currentComments = currentComments == null ?
158                    comment :
159                    currentComments + '\n' + comment;
160            }
161            else
162            {
163                currentLineLength = currentLine.length();
164            }
165
166            // Skip leading whitespace.
167            currentIndex = 0;
168            while (currentIndex < currentLineLength &&
169                   Character.isWhitespace(currentLine.charAt(currentIndex)))
170            {
171                currentIndex++;
172            }
173        }
174
175        // Find the word starting at the current index.
176        int startIndex = currentIndex;
177        int endIndex;
178
179        char startChar = currentLine.charAt(startIndex);
180
181        if (isDelimiter(startChar))
182        {
183            // The next word is a single delimiting character.
184            endIndex = ++currentIndex;
185        }
186        else if (isQuote(startChar))
187        {
188            // The next word is starting with a quote character.
189            // Skip the opening quote.
190            startIndex++;
191
192            // The next word is a quoted character string.
193            // Find the closing quote.
194            do
195            {
196                currentIndex++;
197
198                if (currentIndex == currentLineLength)
199                {
200                    currentWord = currentLine.substring(startIndex-1, currentIndex);
201                    throw new IOException("Missing closing quote for "+locationDescription());
202                }
203            }
204            while (currentLine.charAt(currentIndex) != startChar);
205
206            endIndex = currentIndex++;
207        }
208        else
209        {
210            // The next word is a simple character string.
211            // Find the end of the line, the first delimiter, or the first
212            // white space.
213            while (currentIndex < currentLineLength)
214            {
215                char currentCharacter = currentLine.charAt(currentIndex);
216                if (isDelimiter(currentCharacter) ||
217                    Character.isWhitespace(currentCharacter))
218                {
219                    break;
220                }
221
222                currentIndex++;
223            }
224
225            endIndex = currentIndex;
226        }
227
228        // Remember and return the parsed word.
229        currentWord = currentLine.substring(startIndex, endIndex);
230
231        return currentWord;
232    }
233
234
235    /**
236     * Returns the comments collected before returning the last word.
237     * Starts collecting new comments.
238     *
239     * @return the collected comments, or <code>null</code> if there weren't any.
240     */
241    public String lastComments() throws IOException
242    {
243        if (includeWordReader == null)
244        {
245            String comments = currentComments;
246            currentComments = null;
247            return comments;
248        }
249        else
250        {
251            return includeWordReader.lastComments();
252        }
253    }
254
255
256    /**
257     * Constructs a readable description of the current position in this
258     * WordReader and its included WordReader objects.
259     *
260     * @return the description.
261     */
262    public String locationDescription()
263    {
264        return
265            (includeWordReader == null ?
266                (currentWord == null ?
267                    "end of " :
268                    "'" + currentWord + "' in " ) :
269                (includeWordReader.locationDescription() + ",\n" +
270                 "  included from ")) +
271            lineLocationDescription();
272    }
273
274
275    /**
276     * Reads a line from this WordReader, or from one of its active included
277     * WordReader objects.
278     *
279     * @return the read line.
280     */
281    protected abstract String nextLine() throws IOException;
282
283
284    /**
285     * Returns a readable description of the current WordReader position.
286     *
287     * @return the description.
288     */
289    protected abstract String lineLocationDescription();
290
291
292    /**
293     * Closes the FileWordReader.
294     */
295    public void close() throws IOException
296    {
297        // Close and ditch the included word reader, if any.
298        if (includeWordReader != null)
299        {
300            includeWordReader.close();
301            includeWordReader = null;
302        }
303    }
304
305
306    // Small utility methods.
307
308    private boolean isDelimiter(char character)
309    {
310        return character == '@' ||
311               character == '{' ||
312               character == '}' ||
313               character == '(' ||
314               character == ')' ||
315               character == ',' ||
316               character == ';' ||
317               character == File.pathSeparatorChar;
318    }
319
320
321    private boolean isQuote(char character)
322    {
323        return character == '\'' ||
324               character == '"';
325    }
326}
327