1"""SCons.Tool.JavaCommon 2 3Stuff for processing Java. 4 5""" 6 7# 8# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation 9# 10# Permission is hereby granted, free of charge, to any person obtaining 11# a copy of this software and associated documentation files (the 12# "Software"), to deal in the Software without restriction, including 13# without limitation the rights to use, copy, modify, merge, publish, 14# distribute, sublicense, and/or sell copies of the Software, and to 15# permit persons to whom the Software is furnished to do so, subject to 16# the following conditions: 17# 18# The above copyright notice and this permission notice shall be included 19# in all copies or substantial portions of the Software. 20# 21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 22# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 23# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28# 29 30__revision__ = "src/engine/SCons/Tool/JavaCommon.py 4369 2009/09/19 15:58:29 scons" 31 32import os 33import os.path 34import re 35import string 36 37java_parsing = 1 38 39default_java_version = '1.4' 40 41if java_parsing: 42 # Parse Java files for class names. 43 # 44 # This is a really cool parser from Charles Crain 45 # that finds appropriate class names in Java source. 46 47 # A regular expression that will find, in a java file: 48 # newlines; 49 # double-backslashes; 50 # a single-line comment "//"; 51 # single or double quotes preceeded by a backslash; 52 # single quotes, double quotes, open or close braces, semi-colons, 53 # periods, open or close parentheses; 54 # floating-point numbers; 55 # any alphanumeric token (keyword, class name, specifier); 56 # any alphanumeric token surrounded by angle brackets (generics); 57 # the multi-line comment begin and end tokens /* and */; 58 # array declarations "[]". 59 _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' + 60 r'\d*\.\d*|[A-Za-z_][\w\$\.]*|<[A-Za-z_]\w+>|' + 61 r'/\*|\*/|\[\])') 62 63 class OuterState: 64 """The initial state for parsing a Java file for classes, 65 interfaces, and anonymous inner classes.""" 66 def __init__(self, version=default_java_version): 67 68 if not version in ('1.1', '1.2', '1.3','1.4', '1.5', '1.6', 69 '5', '6'): 70 msg = "Java version %s not supported" % version 71 raise NotImplementedError(msg) 72 73 self.version = version 74 self.listClasses = [] 75 self.listOutputs = [] 76 self.stackBrackets = [] 77 self.brackets = 0 78 self.nextAnon = 1 79 self.localClasses = [] 80 self.stackAnonClassBrackets = [] 81 self.anonStacksStack = [[0]] 82 self.package = None 83 84 def trace(self): 85 pass 86 87 def __getClassState(self): 88 try: 89 return self.classState 90 except AttributeError: 91 ret = ClassState(self) 92 self.classState = ret 93 return ret 94 95 def __getPackageState(self): 96 try: 97 return self.packageState 98 except AttributeError: 99 ret = PackageState(self) 100 self.packageState = ret 101 return ret 102 103 def __getAnonClassState(self): 104 try: 105 return self.anonState 106 except AttributeError: 107 self.outer_state = self 108 ret = SkipState(1, AnonClassState(self)) 109 self.anonState = ret 110 return ret 111 112 def __getSkipState(self): 113 try: 114 return self.skipState 115 except AttributeError: 116 ret = SkipState(1, self) 117 self.skipState = ret 118 return ret 119 120 def __getAnonStack(self): 121 return self.anonStacksStack[-1] 122 123 def openBracket(self): 124 self.brackets = self.brackets + 1 125 126 def closeBracket(self): 127 self.brackets = self.brackets - 1 128 if len(self.stackBrackets) and \ 129 self.brackets == self.stackBrackets[-1]: 130 self.listOutputs.append(string.join(self.listClasses, '$')) 131 self.localClasses.pop() 132 self.listClasses.pop() 133 self.anonStacksStack.pop() 134 self.stackBrackets.pop() 135 if len(self.stackAnonClassBrackets) and \ 136 self.brackets == self.stackAnonClassBrackets[-1]: 137 self.__getAnonStack().pop() 138 self.stackAnonClassBrackets.pop() 139 140 def parseToken(self, token): 141 if token[:2] == '//': 142 return IgnoreState('\n', self) 143 elif token == '/*': 144 return IgnoreState('*/', self) 145 elif token == '{': 146 self.openBracket() 147 elif token == '}': 148 self.closeBracket() 149 elif token in [ '"', "'" ]: 150 return IgnoreState(token, self) 151 elif token == "new": 152 # anonymous inner class 153 if len(self.listClasses) > 0: 154 return self.__getAnonClassState() 155 return self.__getSkipState() # Skip the class name 156 elif token in ['class', 'interface', 'enum']: 157 if len(self.listClasses) == 0: 158 self.nextAnon = 1 159 self.stackBrackets.append(self.brackets) 160 return self.__getClassState() 161 elif token == 'package': 162 return self.__getPackageState() 163 elif token == '.': 164 # Skip the attribute, it might be named "class", in which 165 # case we don't want to treat the following token as 166 # an inner class name... 167 return self.__getSkipState() 168 return self 169 170 def addAnonClass(self): 171 """Add an anonymous inner class""" 172 if self.version in ('1.1', '1.2', '1.3', '1.4'): 173 clazz = self.listClasses[0] 174 self.listOutputs.append('%s$%d' % (clazz, self.nextAnon)) 175 elif self.version in ('1.5', '1.6', '5', '6'): 176 self.stackAnonClassBrackets.append(self.brackets) 177 className = [] 178 className.extend(self.listClasses) 179 self.__getAnonStack()[-1] = self.__getAnonStack()[-1] + 1 180 for anon in self.__getAnonStack(): 181 className.append(str(anon)) 182 self.listOutputs.append(string.join(className, '$')) 183 184 self.nextAnon = self.nextAnon + 1 185 self.__getAnonStack().append(0) 186 187 def setPackage(self, package): 188 self.package = package 189 190 class AnonClassState: 191 """A state that looks for anonymous inner classes.""" 192 def __init__(self, old_state): 193 # outer_state is always an instance of OuterState 194 self.outer_state = old_state.outer_state 195 self.old_state = old_state 196 self.brace_level = 0 197 def parseToken(self, token): 198 # This is an anonymous class if and only if the next 199 # non-whitespace token is a bracket. Everything between 200 # braces should be parsed as normal java code. 201 if token[:2] == '//': 202 return IgnoreState('\n', self) 203 elif token == '/*': 204 return IgnoreState('*/', self) 205 elif token == '\n': 206 return self 207 elif token[0] == '<' and token[-1] == '>': 208 return self 209 elif token == '(': 210 self.brace_level = self.brace_level + 1 211 return self 212 if self.brace_level > 0: 213 if token == 'new': 214 # look further for anonymous inner class 215 return SkipState(1, AnonClassState(self)) 216 elif token in [ '"', "'" ]: 217 return IgnoreState(token, self) 218 elif token == ')': 219 self.brace_level = self.brace_level - 1 220 return self 221 if token == '{': 222 self.outer_state.addAnonClass() 223 return self.old_state.parseToken(token) 224 225 class SkipState: 226 """A state that will skip a specified number of tokens before 227 reverting to the previous state.""" 228 def __init__(self, tokens_to_skip, old_state): 229 self.tokens_to_skip = tokens_to_skip 230 self.old_state = old_state 231 def parseToken(self, token): 232 self.tokens_to_skip = self.tokens_to_skip - 1 233 if self.tokens_to_skip < 1: 234 return self.old_state 235 return self 236 237 class ClassState: 238 """A state we go into when we hit a class or interface keyword.""" 239 def __init__(self, outer_state): 240 # outer_state is always an instance of OuterState 241 self.outer_state = outer_state 242 def parseToken(self, token): 243 # the next non-whitespace token should be the name of the class 244 if token == '\n': 245 return self 246 # If that's an inner class which is declared in a method, it 247 # requires an index prepended to the class-name, e.g. 248 # 'Foo$1Inner' (Tigris Issue 2087) 249 if self.outer_state.localClasses and \ 250 self.outer_state.stackBrackets[-1] > \ 251 self.outer_state.stackBrackets[-2]+1: 252 locals = self.outer_state.localClasses[-1] 253 try: 254 idx = locals[token] 255 locals[token] = locals[token]+1 256 except KeyError: 257 locals[token] = 1 258 token = str(locals[token]) + token 259 self.outer_state.localClasses.append({}) 260 self.outer_state.listClasses.append(token) 261 self.outer_state.anonStacksStack.append([0]) 262 return self.outer_state 263 264 class IgnoreState: 265 """A state that will ignore all tokens until it gets to a 266 specified token.""" 267 def __init__(self, ignore_until, old_state): 268 self.ignore_until = ignore_until 269 self.old_state = old_state 270 def parseToken(self, token): 271 if self.ignore_until == token: 272 return self.old_state 273 return self 274 275 class PackageState: 276 """The state we enter when we encounter the package keyword. 277 We assume the next token will be the package name.""" 278 def __init__(self, outer_state): 279 # outer_state is always an instance of OuterState 280 self.outer_state = outer_state 281 def parseToken(self, token): 282 self.outer_state.setPackage(token) 283 return self.outer_state 284 285 def parse_java_file(fn, version=default_java_version): 286 return parse_java(open(fn, 'r').read(), version) 287 288 def parse_java(contents, version=default_java_version, trace=None): 289 """Parse a .java file and return a double of package directory, 290 plus a list of .class files that compiling that .java file will 291 produce""" 292 package = None 293 initial = OuterState(version) 294 currstate = initial 295 for token in _reToken.findall(contents): 296 # The regex produces a bunch of groups, but only one will 297 # have anything in it. 298 currstate = currstate.parseToken(token) 299 if trace: trace(token, currstate) 300 if initial.package: 301 package = string.replace(initial.package, '.', os.sep) 302 return (package, initial.listOutputs) 303 304else: 305 # Don't actually parse Java files for class names. 306 # 307 # We might make this a configurable option in the future if 308 # Java-file parsing takes too long (although it shouldn't relative 309 # to how long the Java compiler itself seems to take...). 310 311 def parse_java_file(fn): 312 """ "Parse" a .java file. 313 314 This actually just splits the file name, so the assumption here 315 is that the file name matches the public class name, and that 316 the path to the file is the same as the package name. 317 """ 318 return os.path.split(file) 319 320# Local Variables: 321# tab-width:4 322# indent-tabs-mode:nil 323# End: 324# vim: set expandtab tabstop=4 shiftwidth=4: 325