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