1 """SCons.Tool.JavaCommon
3 Stuff for processing Java.
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__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
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;
49 # single or double quotes preceeded by a backslash; array
51 _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}]|[A-Za-z_][\w\.]*|' +
55 """The initial state for parsing a Java file for classes,
56 interfaces, and anonymous inner classes."""
60 self.stackBrackets = []
65 def __getClassState(self):
67 return self.classState
68 except AttributeError:
69 ret = ClassState(self)
73 def __getPackageState(self):
75 return self.packageState
76 except AttributeError:
77 ret = PackageState(self)
78 self.packageState = ret
81 def __getAnonClassState(self):
84 except AttributeError:
85 ret = SkipState(1, AnonClassState(self))
89 def __getSkipState(self):
92 except AttributeError:
93 ret = SkipState(1, self)
97 def parseToken(self, token):
99 return IgnoreState('\n', self)
101 return IgnoreState('*/', self)
103 self.brackets = self.brackets + 1
105 self.brackets = self.brackets - 1
106 if len(self.stackBrackets) and \
107 self.brackets == self.stackBrackets[-1]:
108 self.listOutputs.append(string.join(self.listClasses, '$'))
109 self.listClasses.pop()
110 self.stackBrackets.pop()
111 elif token == '"' or token == "'":
112 return IgnoreState(token, self)
114 # anonymous inner class
115 if len(self.listClasses) > 0:
116 return self.__getAnonClassState()
117 return self.__getSkipState() # Skip the class name
118 elif token in ['class', 'interface', 'enum']:
119 if len(self.listClasses) == 0:
121 self.stackBrackets.append(self.brackets)
122 return self.__getClassState()
123 elif token == 'package':
124 return self.__getPackageState()
127 def addAnonClass(self):
128 """Add an anonymous inner class"""
129 clazz = self.listClasses[0]
130 self.listOutputs.append('%s$%d' % (clazz, self.nextAnon))
131 self.brackets = self.brackets + 1
132 self.nextAnon = self.nextAnon + 1
134 def setPackage(self, package):
135 self.package = package
137 class AnonClassState:
138 """A state that looks for anonymous inner classes."""
139 def __init__(self, outer_state):
140 # outer_state is always an instance of OuterState
141 self.outer_state = outer_state
142 self.tokens_to_find = 2
143 def parseToken(self, token):
144 # This is an anonymous class if and only if the next token
147 self.outer_state.addAnonClass()
148 elif token in ['"', "'"]:
149 return IgnoreState(token, self)
150 return self.outer_state
153 """A state that will skip a specified number of tokens before
154 reverting to the previous state."""
155 def __init__(self, tokens_to_skip, old_state):
156 self.tokens_to_skip = tokens_to_skip
157 self.old_state = old_state
158 def parseToken(self, token):
159 self.tokens_to_skip = self.tokens_to_skip - 1
160 if self.tokens_to_skip < 1:
161 return self.old_state
165 """A state we go into when we hit a class or interface keyword."""
166 def __init__(self, outer_state):
167 # outer_state is always an instance of OuterState
168 self.outer_state = outer_state
169 def parseToken(self, token):
170 # the next non-whitespace token should be the name of the class
173 self.outer_state.listClasses.append(token)
174 return self.outer_state
177 """A state that will ignore all tokens until it gets to a
179 def __init__(self, ignore_until, old_state):
180 self.ignore_until = ignore_until
181 self.old_state = old_state
182 def parseToken(self, token):
183 if self.ignore_until == token:
184 return self.old_state
188 """The state we enter when we encounter the package keyword.
189 We assume the next token will be the package name."""
190 def __init__(self, outer_state):
191 # outer_state is always an instance of OuterState
192 self.outer_state = outer_state
193 def parseToken(self, token):
194 self.outer_state.setPackage(token)
195 return self.outer_state
197 def parse_java_file(fn):
198 return parse_java(open(fn, 'r').read())
200 def parse_java(contents):
201 """Parse a .java file and return a double of package directory,
202 plus a list of .class files that compiling that .java file will
205 initial = OuterState()
207 for token in _reToken.findall(contents):
208 # The regex produces a bunch of groups, but only one will
209 # have anything in it.
210 currstate = currstate.parseToken(token)
212 package = string.replace(initial.package, '.', os.sep)
213 return (package, initial.listOutputs)
216 # Don't actually parse Java files for class names.
218 # We might make this a configurable option in the future if
219 # Java-file parsing takes too long (although it shouldn't relative
220 # to how long the Java compiler itself seems to take...).
222 def parse_java_file(fn):
223 """ "Parse" a .java file.
225 This actually just splits the file name, so the assumption here
226 is that the file name matches the public class name, and that
227 the path to the file is the same as the package name.
229 return os.path.split(file)