• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
21 package proguard;
22 
23 import 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  */
35 public 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      */
WordReader(File baseDir)52     protected WordReader(File baseDir)
53     {
54         this.baseDir = baseDir;
55     }
56 
57 
58     /**
59      * Sets the base directory of this reader.
60      */
setBaseDir(File baseDir)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      */
getBaseDir()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      */
includeWordReader(WordReader newIncludeWordReader)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      */
nextWord()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      */
lastComments()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      */
locationDescription()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      */
nextLine()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      */
lineLocationDescription()289     protected abstract String lineLocationDescription();
290 
291 
292     /**
293      * Closes the FileWordReader.
294      */
close()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 
isDelimiter(char character)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 
isQuote(char character)321     private boolean isQuote(char character)
322     {
323         return character == '\'' ||
324                character == '"';
325     }
326 }
327