1
"""SCons.Tool.JavaCommon
3
Stuff for processing Java.
8
# Copyright (c) 2001, 2002, 2003, 2004 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__ = "/home/scons/scons/branch.0/baseline/src/engine/SCons/Tool/JavaCommon.py 0.96.1.D001 2004/08/23 09:55:29 knight"
40
# Parse Java files for class names.
42
# This is a really cool parser from Charles Crain
43
# that finds appropriate class names in Java source.
45
# A regular expression that will find, in a java file: newlines;
46
# any alphanumeric token (keyword, class name, specifier); open or
47
# close brackets; a single-line comment "//"; the multi-line comment
48
# begin and end tokens /* and */; single or double quotes; and
49
# single or double quotes preceeded by a backslash.
50
_reToken = re.compile(r'(\n|//|\\[\'"]|[\'"\{\}]|[A-Za-z_][\w\.]*|' +
54
"""The initial state for parsing a Java file for classes,
55
interfaces, and anonymous inner classes."""
59
self.stackBrackets = []
64
def __getClassState(self):
66
return self.classState
67
except AttributeError:
68
ret = ClassState(self)
72
def __getPackageState(self):
74
return self.packageState
75
except AttributeError:
76
ret = PackageState(self)
77
self.packageState = ret
80
def __getAnonClassState(self):
83
except AttributeError:
84
ret = SkipState(1, AnonClassState(self))
88
def __getSkipState(self):
91
except AttributeError:
92
ret = SkipState(1, self)
96
def parseToken(self, token):
98
return IgnoreState('\n', self)
100
return IgnoreState('*/', self)
102
self.brackets = self.brackets + 1
104
self.brackets = self.brackets - 1
105
if len(self.stackBrackets) and \
106
self.brackets == self.stackBrackets[-1]:
107
self.listOutputs.append(string.join(self.listClasses, '$'))
108
self.listClasses.pop()
109
self.stackBrackets.pop()
110
elif token == '"' or token == "'":
111
return IgnoreState(token, self)
113
# anonymous inner class
114
if len(self.listClasses) > 0:
115
return self.__getAnonClassState()
116
return self.__getSkipState() # Skip the class name
117
elif token == 'class' or token == 'interface':
118
if len(self.listClasses) == 0:
120
self.stackBrackets.append(self.brackets)
121
return self.__getClassState()
122
elif token == 'package':
123
return self.__getPackageState()
126
def addAnonClass(self):
127
"""Add an anonymous inner class"""
128
clazz = self.listClasses[0]
129
self.listOutputs.append('%s$%d' % (clazz, self.nextAnon))
130
self.brackets = self.brackets + 1
131
self.nextAnon = self.nextAnon + 1
133
def setPackage(self, package):
134
self.package = package
136
class AnonClassState:
137
"""A state that looks for anonymous inner classes."""
138
def __init__(self, outer_state):
139
# outer_state is always an instance of OuterState
140
self.outer_state = outer_state
141
self.tokens_to_find = 2
142
def parseToken(self, token):
143
# This is an anonymous class if and only if the next token is a bracket
145
self.outer_state.addAnonClass()
146
return self.outer_state
149
"""A state that will skip a specified number of tokens before
150
reverting to the previous state."""
151
def __init__(self, tokens_to_skip, old_state):
152
self.tokens_to_skip = tokens_to_skip
153
self.old_state = old_state
154
def parseToken(self, token):
155
self.tokens_to_skip = self.tokens_to_skip - 1
156
if self.tokens_to_skip < 1:
157
return self.old_state
161
"""A state we go into when we hit a class or interface keyword."""
162
def __init__(self, outer_state):
163
# outer_state is always an instance of OuterState
164
self.outer_state = outer_state
165
def parseToken(self, token):
166
# the next non-whitespace token should be the name of the class
169
self.outer_state.listClasses.append(token)
170
return self.outer_state
173
"""A state that will ignore all tokens until it gets to a
175
def __init__(self, ignore_until, old_state):
176
self.ignore_until = ignore_until
177
self.old_state = old_state
178
def parseToken(self, token):
179
if self.ignore_until == token:
180
return self.old_state
184
"""The state we enter when we encounter the package keyword.
185
We assume the next token will be the package name."""
186
def __init__(self, outer_state):
187
# outer_state is always an instance of OuterState
188
self.outer_state = outer_state
189
def parseToken(self, token):
190
self.outer_state.setPackage(token)
191
return self.outer_state
193
def parse_java_file(fn):
194
return parse_java(open(fn, 'r').read())
196
def parse_java(contents):
197
"""Parse a .java file and return a double of package directory,
198
plus a list of .class files that compiling that .java file will
201
initial = OuterState()
203
for token in _reToken.findall(contents):
204
# The regex produces a bunch of groups, but only one will
205
# have anything in it.
206
currstate = currstate.parseToken(token)
208
package = string.replace(initial.package, '.', os.sep)
209
return (package, initial.listOutputs)
212
# Don't actually parse Java files for class names.
214
# We might make this a configurable option in the future if
215
# Java-file parsing takes too long (although it shouldn't relative
216
# to how long the Java compiler itself seems to take...).
218
def parse_java_file(fn):
219
""" "Parse" a .java file.
221
This actually just splits the file name, so the assumption here
222
is that the file name matches the public class name, and that
223
the path to the file is the same as the package name.
225
return os.path.split(file)