summaryrefslogtreecommitdiffstats
path: root/src/engine/SCons/Tool/javac.py
diff options
context:
space:
mode:
authorSteven Knight <knight@baldmt.com>2003-04-16 06:20:26 (GMT)
committerSteven Knight <knight@baldmt.com>2003-04-16 06:20:26 (GMT)
commit5be86ed2d7fb824f775ca5159b93f045ac8cb56d (patch)
tree52e69d55e83393752a988e42dc2a2db0f268d101 /src/engine/SCons/Tool/javac.py
parent9de8ce86148b2b041ee81c62a1b2fe08bd1e7473 (diff)
downloadSCons-5be86ed2d7fb824f775ca5159b93f045ac8cb56d.zip
SCons-5be86ed2d7fb824f775ca5159b93f045ac8cb56d.tar.gz
SCons-5be86ed2d7fb824f775ca5159b93f045ac8cb56d.tar.bz2
Really parse .java files for inner class names. (Charles Crain)
Diffstat (limited to 'src/engine/SCons/Tool/javac.py')
-rw-r--r--src/engine/SCons/Tool/javac.py196
1 files changed, 110 insertions, 86 deletions
diff --git a/src/engine/SCons/Tool/javac.py b/src/engine/SCons/Tool/javac.py
index e2d4ecb..60233b3 100644
--- a/src/engine/SCons/Tool/javac.py
+++ b/src/engine/SCons/Tool/javac.py
@@ -34,107 +34,123 @@ selection method.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
import os.path
+import re
import string
import SCons.Builder
-# Okay, I don't really know what configurability would be good for
-# parsing Java files for package and/or class names, but it was easy, so
-# here it is.
-#
-# Set java_parsing to the following values to enable three different
-# flavors of parsing:
-#
-# 0 The file isn't actually parsed, so this will be quickest. The
-# package + class name is assumed to be the file path name, and we
-# just split the path name. This breaks if a package name will
-# ever be different from the path to the .java file.
-#
-# 1 The file is read to find the package name, after which we stop.
-# This should be pretty darn quick, and allows flexibility in
-# package names, but assumes that the public class name in the
-# file matches the file name. This seems to be a good assumption
-# because, for example, if you try to declare a public class
-# with a different name from the file, javac tells you:
-#
-# class Foo is public, should be declared in a file named Foo.java
-#
-# 2 Full flexibility of class names. We parse for the package name
-# (like level #1) but the output .class file name is assumed to
-# match the declared public class name--and, as a bonus, this will
-# actually support multiple public classes in a single file. My
-# guess is that's illegal Java, though... Or is it an option
-# supported by some compilers?
-#
java_parsing = 1
-if java_parsing == 0:
- def parse_java(file, suffix):
- """ "Parse" a .java file.
-
- This actually just splits the file name, so the assumption here
- is that the file name matches the public class name, and that
- the path to the file is the same as the package name.
- """
- return os.path.split(file)
-elif java_parsing == 1:
- def parse_java(file, suffix):
- """Parse a .java file for a package name.
-
- This, of course, is not full parsing of Java files, but
- simple-minded searching for the usual begins-in-column-1
- "package" string most Java programs use to define their package.
- """
+if java_parsing:
+ # Parse Java files for class names.
+ #
+ # This is a really simple and cool parser from Charles Crain
+ # that finds appropriate class names in Java source.
+
+ _reToken = re.compile(r'[^\\]([\'"])|([\{\}])|' +
+ r'(?:^|[\{\}\s;])((?:class|interface)'+
+ r'\s+[A-Za-z_]\w*)|' +
+ r'(new\s+[A-Za-z_]\w*\s*\([^\)]*\)\s*\{)|' +
+ r'(//[^\r\n]*)|(/\*|\*/)')
+
+ class OuterState:
+ def __init__(self):
+ self.listClasses = []
+ self.listOutputs = []
+ self.stackBrackets = []
+ self.brackets = 0
+ self.nextAnon = 1
+
+ def parseToken(self, token):
+ #print token
+ if token[:2] == '//':
+ pass # ignore comment
+ elif token == '/*':
+ return IgnoreState('*/', self)
+ elif token == '{':
+ self.brackets = self.brackets + 1
+ elif token == '}':
+ self.brackets = self.brackets - 1
+ if len(self.stackBrackets) and \
+ self.brackets == self.stackBrackets[-1]:
+ self.listOutputs.append(string.join(self.listClasses, '$'))
+ self.listClasses.pop()
+ self.stackBrackets.pop()
+ elif token == '"':
+ return IgnoreState('"', self)
+ elif token == "'":
+ return IgnoreState("'", self)
+ elif token[:3] == "new":
+ # anonymous inner class
+ if len(self.listClasses) > 0:
+ clazz = self.listClasses[0]
+ self.listOutputs.append('%s$%d' % (clazz, self.nextAnon))
+ self.brackets = self.brackets + 1
+ self.nextAnon = self.nextAnon + 1
+ elif token[:5] == 'class':
+ if len(self.listClasses) == 0:
+ self.nextAnon = 1
+ self.listClasses.append(string.join(string.split(token[6:])))
+ self.stackBrackets.append(self.brackets)
+ elif token[:9] == 'interface':
+ if len(self.listClasses) == 0:
+ self.nextAnon = 1
+ self.listClasses.append(string.join(string.split(token[10:])))
+ self.stackBrackets.append(self.brackets)
+ return self
+
+ class IgnoreState:
+ def __init__(self, ignore_until, old_state):
+ self.ignore_until = ignore_until
+ self.old_state = old_state
+ def parseToken(self, token):
+ if token == self.ignore_until:
+ return self.old_state
+ return self
+
+ def parse_java(file):
+ contents = open(file, 'r').read()
+
+ # Is there a more efficient way to do this than to split
+ # the contents like this?
pkg_dir = None
- classes = []
- f = open(file, "rb")
- while 1:
- line = f.readline()
- if not line:
- break
+ for line in string.split(contents, "\n"):
if line[:7] == 'package':
pkg = string.split(line)[1]
if pkg[-1] == ';':
pkg = pkg[:-1]
pkg_dir = apply(os.path.join, string.split(pkg, '.'))
- classes = [ os.path.split(file[:-len(suffix)])[1] ]
break
- f.close()
- return pkg_dir, classes
-
-elif java_parsing == 2:
- import re
- pub_re = re.compile('^\s*public(\s+abstract)?\s+class\s+(\S+)')
- def parse_java(file, suffix):
- """Parse a .java file for package name and classes.
-
- This, of course, is not full parsing of Java files, but
- simple-minded searching for the usual strings most Java programs
- seem to use for packages and public class names.
+
+ initial = OuterState()
+ currstate = initial
+ for matches in _reToken.findall(contents):
+ # The regex produces a bunch of groups, but only one will
+ # have anything in it.
+ token = filter(lambda x: x, matches)[0]
+ currstate = currstate.parseToken(token)
+
+ return pkg_dir, initial.listOutputs
+
+else:
+ # Don't actually parse Java files for class names.
+ #
+ # We might make this a configurable option in the future if
+ # Java-file parsing takes too long (although it shouldn't relative
+ # to how long the Java compiler itself seems to take...).
+
+ def parse_java(file):
+ """ "Parse" a .java file.
+
+ This actually just splits the file name, so the assumption here
+ is that the file name matches the public class name, and that
+ the path to the file is the same as the package name.
"""
- pkg_dir = None
- classes = []
- f = open(file, "rb")
- while 1:
- line = f.readline()
- if not line:
- break
- if line[:7] == 'package':
- pkg = string.split(line)[1]
- if pkg[-1] == ';':
- pkg = pkg[:-1]
- pkg_dir = apply(os.path.join, string.split(pkg, '.'))
- elif line[:6] == 'public':
- c = pub_re.findall(line)
- try:
- classes.append(c[0][1])
- except IndexError:
- pass
- f.close()
- return pkg_dir, classes
+ return os.path.split(file)
def generate(env):
"""Add Builders and construction variables for javac to an Environment."""
+
def emit_java_files(target, source, env):
"""Create and return lists of source java files
and their corresponding target class files.
@@ -143,6 +159,7 @@ def generate(env):
env['_JAVASRCDIR'] = source[0]
java_suffix = env.get('JAVASUFFIX', '.java')
class_suffix = env.get('JAVACLASSSUFFIX', '.class')
+
slist = []
def visit(arg, dirname, names, js=java_suffix):
java_files = filter(lambda n, js=js: n[-len(js):] == js, names)
@@ -151,17 +168,24 @@ def generate(env):
java_files)
arg.extend(java_paths)
os.path.walk(source[0], visit, slist)
+
tlist = []
for file in slist:
- pkg_dir, classes = parse_java(file, java_suffix)
+ pkg_dir, classes = parse_java(file)
if pkg_dir:
for c in classes:
tlist.append(os.path.join(target[0],
pkg_dir,
c + class_suffix))
+ elif classes:
+ for c in classes:
+ tlist.append(os.path.join(target[0], c + class_suffix))
else:
+ # This is an odd end case: no package and no classes.
+ # Just do our best based on the source file name.
tlist.append(os.path.join(target[0],
- file[:-5] + class_suffix))
+ file[:-len(java_suffix)] + class_suffix))
+
return tlist, slist
JavaBuilder = SCons.Builder.Builder(action = '$JAVACCOM',