• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * ProGuard -- shrinking, optimization, obfuscation, and preverification
3  *             of Java bytecode.
4  *
5  * Copyright (c) 2002-2013 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      * @param isFileName return a complete line (or argument), if the word
109      *                      isn't an option (it doesn't start with '-').
110      * @return the read word.
111      */
nextWord(boolean isFileName)112     public String nextWord(boolean isFileName) throws IOException
113     {
114         currentWord = null;
115 
116         // See if we have an included reader to produce a word.
117         if (includeWordReader != null)
118         {
119             // Does the included word reader still produce a word?
120             currentWord = includeWordReader.nextWord(isFileName);
121             if (currentWord != null)
122             {
123                 // Return it if so.
124                 return currentWord;
125             }
126 
127             // Otherwise close and ditch the word reader.
128             includeWordReader.close();
129             includeWordReader = null;
130         }
131 
132         // Get a word from this reader.
133 
134         // Skip any whitespace and comments left on the current line.
135         if (currentLine != null)
136         {
137             // Skip any leading whitespace.
138             while (currentIndex < currentLineLength &&
139                    Character.isWhitespace(currentLine.charAt(currentIndex)))
140             {
141                 currentIndex++;
142             }
143 
144             // Skip any comments.
145             if (currentIndex < currentLineLength &&
146                 isComment(currentLine.charAt(currentIndex)))
147             {
148                 currentIndex = currentLineLength;
149             }
150         }
151 
152         // Make sure we have a non-blank line.
153         while (currentLine == null || currentIndex == currentLineLength)
154         {
155             currentLine = nextLine();
156             if (currentLine == null)
157             {
158                 return null;
159             }
160 
161             currentLineLength = currentLine.length();
162 
163             // Skip any leading whitespace.
164             currentIndex = 0;
165             while (currentIndex < currentLineLength &&
166                    Character.isWhitespace(currentLine.charAt(currentIndex)))
167             {
168                 currentIndex++;
169             }
170 
171             // Remember any leading comments.
172             if (currentIndex < currentLineLength &&
173                 isComment(currentLine.charAt(currentIndex)))
174             {
175                 // Remember the comments.
176                 String comment = currentLine.substring(currentIndex + 1);
177                 currentComments = currentComments == null ?
178                     comment :
179                     currentComments + '\n' + comment;
180 
181                 // Skip the comments.
182                 currentIndex = currentLineLength;
183             }
184         }
185 
186         // Find the word starting at the current index.
187         int startIndex = currentIndex;
188         int endIndex;
189 
190         char startChar = currentLine.charAt(startIndex);
191 
192         if (isQuote(startChar))
193         {
194             // The next word is starting with a quote character.
195             // Skip the opening quote.
196             startIndex++;
197 
198             // The next word is a quoted character string.
199             // Find the closing quote.
200             do
201             {
202                 currentIndex++;
203 
204                 if (currentIndex == currentLineLength)
205                 {
206                     currentWord = currentLine.substring(startIndex-1, currentIndex);
207                     throw new IOException("Missing closing quote for "+locationDescription());
208                 }
209             }
210             while (currentLine.charAt(currentIndex) != startChar);
211 
212             endIndex = currentIndex++;
213         }
214         else if (isFileName &&
215                  !isOption(startChar))
216         {
217             // The next word is a (possibly optional) file name.
218             // Find the end of the line, the first path separator, the first
219             // option, or the first comment.
220             while (currentIndex < currentLineLength)
221             {
222                 char currentCharacter = currentLine.charAt(currentIndex);
223                 if (isFileDelimiter(currentCharacter) ||
224                     ((isOption(currentCharacter) ||
225                       isComment(currentCharacter)) &&
226                      Character.isWhitespace(currentLine.charAt(currentIndex-1)))) {
227                     break;
228                 }
229 
230                 currentIndex++;
231             }
232 
233             endIndex = currentIndex;
234 
235             // Trim any trailing whitespace.
236             while (endIndex > startIndex &&
237                    Character.isWhitespace(currentLine.charAt(endIndex-1)))
238             {
239                 endIndex--;
240             }
241         }
242         else if (isDelimiter(startChar))
243         {
244             // The next word is a single delimiting character.
245             endIndex = ++currentIndex;
246         }
247         else
248         {
249             // The next word is a simple character string.
250             // Find the end of the line, the first delimiter, or the first
251             // white space.
252             while (currentIndex < currentLineLength)
253             {
254                 char currentCharacter = currentLine.charAt(currentIndex);
255                 if (isDelimiter(currentCharacter)            ||
256                     Character.isWhitespace(currentCharacter) ||
257                     isComment(currentCharacter)) {
258                     break;
259                 }
260 
261                 currentIndex++;
262             }
263 
264             endIndex = currentIndex;
265         }
266 
267         // Remember and return the parsed word.
268         currentWord = currentLine.substring(startIndex, endIndex);
269 
270         return currentWord;
271     }
272 
273 
274     /**
275      * Returns the comments collected before returning the last word.
276      * Starts collecting new comments.
277      *
278      * @return the collected comments, or <code>null</code> if there weren't any.
279      */
lastComments()280     public String lastComments() throws IOException
281     {
282         if (includeWordReader == null)
283         {
284             String comments = currentComments;
285             currentComments = null;
286             return comments;
287         }
288         else
289         {
290             return includeWordReader.lastComments();
291         }
292     }
293 
294 
295     /**
296      * Constructs a readable description of the current position in this
297      * WordReader and its included WordReader objects.
298      *
299      * @return the description.
300      */
locationDescription()301     public String locationDescription()
302     {
303         return
304             (includeWordReader == null ?
305                 (currentWord == null ?
306                     "end of " :
307                     "'" + currentWord + "' in " ) :
308                 (includeWordReader.locationDescription() + ",\n" +
309                  "  included from ")) +
310             lineLocationDescription();
311     }
312 
313 
314     /**
315      * Reads a line from this WordReader, or from one of its active included
316      * WordReader objects.
317      *
318      * @return the read line.
319      */
nextLine()320     protected abstract String nextLine() throws IOException;
321 
322 
323     /**
324      * Returns a readable description of the current WordReader position.
325      *
326      * @return the description.
327      */
lineLocationDescription()328     protected abstract String lineLocationDescription();
329 
330 
331     /**
332      * Closes the FileWordReader.
333      */
close()334     public void close() throws IOException
335     {
336         // Close and ditch the included word reader, if any.
337         if (includeWordReader != null)
338         {
339             includeWordReader.close();
340             includeWordReader = null;
341         }
342     }
343 
344 
345     // Small utility methods.
346 
isOption(char character)347     private boolean isOption(char character)
348     {
349         return character == '-';
350     }
351 
352 
isComment(char character)353     private boolean isComment(char character)
354     {
355         return character == COMMENT_CHARACTER;
356     }
357 
358 
isDelimiter(char character)359     private boolean isDelimiter(char character)
360     {
361         return character == '@' ||
362                character == '{' ||
363                character == '}' ||
364                character == '(' ||
365                character == ')' ||
366                character == ',' ||
367                character == ';' ||
368                character == File.pathSeparatorChar;
369     }
370 
371 
isFileDelimiter(char character)372     private boolean isFileDelimiter(char character)
373     {
374         return character == '(' ||
375                character == ')' ||
376                character == ',' ||
377                character == ';' ||
378                character == File.pathSeparatorChar;
379     }
380 
381 
isQuote(char character)382     private boolean isQuote(char character)
383     {
384         return character == '\'' ||
385                character == '"';
386     }
387 }
388