1
"""SCons.Tool.JavaCommon
3
Stuff for processing Java.
8
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation
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:
18
# The above copyright notice and this permission notice shall be included
19
# in all copies or substantial portions of the Software.
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.
30
__revision__ = "src/engine/SCons/Tool/JavaCommon.py 5134 2010/08/16 23:02:40 bdeegan"
38
default_java_version = '1.4'
41
# Parse Java files for class names.
43
# This is a really cool parser from Charles Crain
44
# that finds appropriate class names in Java source.
46
# A regular expression that will find, in a java file:
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+>|' +
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):
67
if not version in ('1.1', '1.2', '1.3','1.4', '1.5', '1.6',
69
msg = "Java version %s not supported" % version
70
raise NotImplementedError(msg)
72
self.version = version
75
self.stackBrackets = []
78
self.localClasses = []
79
self.stackAnonClassBrackets = []
80
self.anonStacksStack = [[0]]
86
def __getClassState(self):
88
return self.classState
89
except AttributeError:
90
ret = ClassState(self)
94
def __getPackageState(self):
96
return self.packageState
97
except AttributeError:
98
ret = PackageState(self)
99
self.packageState = ret
102
def __getAnonClassState(self):
104
return self.anonState
105
except AttributeError:
106
self.outer_state = self
107
ret = SkipState(1, AnonClassState(self))
111
def __getSkipState(self):
113
return self.skipState
114
except AttributeError:
115
ret = SkipState(1, self)
119
def __getAnonStack(self):
120
return self.anonStacksStack[-1]
122
def openBracket(self):
123
self.brackets = self.brackets + 1
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()
139
def parseToken(self, token):
140
if token[:2] == '//':
141
return IgnoreState('\n', self)
143
return IgnoreState('*/', self)
148
elif token in [ '"', "'" ]:
149
return IgnoreState(token, self)
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:
158
self.stackBrackets.append(self.brackets)
159
return self.__getClassState()
160
elif token == 'package':
161
return self.__getPackageState()
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()
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)
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))
183
self.nextAnon = self.nextAnon + 1
184
self.__getAnonStack().append(0)
186
def setPackage(self, package):
187
self.package = package
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
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)
203
return IgnoreState('*/', self)
206
elif token[0] == '<' and token[-1] == '>':
209
self.brace_level = self.brace_level + 1
211
if self.brace_level > 0:
213
# look further for anonymous inner class
214
return SkipState(1, AnonClassState(self))
215
elif token in [ '"', "'" ]:
216
return IgnoreState(token, self)
218
self.brace_level = self.brace_level - 1
221
self.outer_state.addAnonClass()
222
return self.old_state.parseToken(token)
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
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
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]
254
locals[token] = 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
263
class IgnoreState(object):
264
"""A state that will ignore all tokens until it gets to a
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
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
284
def parse_java_file(fn, version=default_java_version):
285
return parse_java(open(fn, 'r').read(), version)
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
292
initial = OuterState(version)
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)
300
package = initial.package.replace('.', os.sep)
301
return (package, initial.listOutputs)
304
# Don't actually parse Java files for class names.
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...).
310
def parse_java_file(fn):
311
""" "Parse" a .java file.
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.
317
return os.path.split(file)
321
# indent-tabs-mode:nil
323
# vim: set expandtab tabstop=4 shiftwidth=4: