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