diff options
author | Endi Sukma Dewata <edewata@redhat.com> | 2012-03-24 02:27:47 -0500 |
---|---|---|
committer | Endi Sukma Dewata <edewata@redhat.com> | 2012-03-26 11:43:54 -0500 |
commit | 621d9e5c413e561293d7484b93882d985b3fe15f (patch) | |
tree | 638f3d75761c121d9a8fb50b52a12a6686c5ac5c /tools/jar | |
parent | 40d3643b8d91886bf210aa27f711731c81a11e49 (diff) | |
download | pki-621d9e5c413e561293d7484b93882d985b3fe15f.tar.gz pki-621d9e5c413e561293d7484b93882d985b3fe15f.tar.xz pki-621d9e5c413e561293d7484b93882d985b3fe15f.zip |
Removed unnecessary pki folder.
Previously the source code was located inside a pki folder.
This folder was created during svn migration and is no longer
needed. This folder has now been removed and the contents have
been moved up one level.
Ticket #131
Diffstat (limited to 'tools/jar')
-rw-r--r-- | tools/jar/README.jar-tools | 281 | ||||
-rwxr-xr-x | tools/jar/jar-query | 542 | ||||
-rwxr-xr-x | tools/jar/java-imports | 195 |
3 files changed, 1018 insertions, 0 deletions
diff --git a/tools/jar/README.jar-tools b/tools/jar/README.jar-tools new file mode 100644 index 000000000..e165fd8bd --- /dev/null +++ b/tools/jar/README.jar-tools @@ -0,0 +1,281 @@ +Q: What does jar-query do? + +A: It scans a set of directories containing jars, it opens every jar + it finds and records the classes in that jar. It stores that + information and permits queries to be run against it. + +Q: What kind of information can jar-query give me? + +A: * Given a class list which jars provide it + + * Given an import specification list which jars provide the + classes which match the specification + + * List which classes appear in more than one jar + (e.g. multiple definitions) and which jars they appear in. + + * For classes with multiple definitions determine which classes have + the same implementation (e.g. a copy) and which have different + implementations (typically different versions). + + * Show the symbolic links which point to a given jar (full link + traversal). + + * List which RPM provides a given jar. + + * List closed set of which jars and RPM's are necessary to resolve + a set of classes (e.g. what jars/RPMs are necessary to build/run) + +Q: Can jar-query give me information about jars not installed on my + system? + +A: No. If the jar isn't installed or isn't in the set of directories + jar-query is told to scan no information will be available for + those classes. This is a little bit of a chicken and egg problem, + you might not know you need to install a jar that contains a class + you need. Not much can be done about that though. + +Q: What does java-imports do? + +A: It locates a set of Java source files in one or more directories + and extracts the import specifications from each source file. It + then lists on stdout the set of unique import specifications. This + is useful as input to jar-query. + +Q: What kind of information can java-imports give me? + +A: * The unique import specifications across a collection of java + files. + + * The list of java files each unique import specification appears + in. + + * The list of java files given a set of directories and exclude + filters (e.g. the files it will scan) + +Q: How do I control which java files java-imports scans? + +A: There are 3 basic controls. The set of paths provided on the + command line. A path may be either a plain java file or a + directory. The -r (--recursive) argument controls whether directories + are recursively scanned or not. One or more exclude regular + expressions can be provided via the -x (--exclude) argument. The + regular expression is tested against each path candidate, if any of + the exclude regular expressions match the path is discarded. + +Q: Which directories does jar-query scan and can I modify that? + +A: By default jar-query scans the system default jar directory and the + system default jni directory. Running jar-query with the help + argument (-h) will print these out. You can add any number of + additional directories to scan with the -d (--dir) argument. If you + don't want to include the default directories you can specify the + -D (--clear-dirs) argument which will zero out the existing + directory list, then add your directories with one or more -d + arguments. + +Q: I want jar-query to ignore some jars, can I do that? + +A: Yes. Use the -x (--exclude) argument. It is a regular expression + pattern applied to a jar path name, if any of the exclude regular + expressions match the jar will be ignored. Multiple exclude + patterns may be specified. + +Q: How does jar-query handle symbolic links? + +A: It's common for a directory to have symbolic links which point jar + files. Typically this occurs when an unversioned name + (e.g. foo.jar) points to a specific jar version + (e.g. foo-1.2.jar). Sometimes links are established for backward + compatibility when jar names change. + + jar-query is designed to tell you the ACTUAL jar file a class is + located in. Which one of the (many) links which point to it are + usually not of interest and would complicate the + reporting. Therefore jar-query never gives link names, it always + does a full link traversal and reports only the ACTUAL jar + file. However, sometimes it's useful to know how an ACTUAL jar file + is pointed to by various links. You can use the -L (--links) + argument which will dump out the link traversal information for + every ACTUAL jar file located. + +Q: How are class names matched in jar-query? + +A: By default the match is done as if the class is a import + specification with support for wildcards + (e.g. org.company.util.*). If the -R (--regexp) argument is + provided matches are done using a general purpose regular + expression. In the special case of interactive use class names will + auto-complete (via TAB) up to the next dot. + +Q: jar-query must build a database of class information each time it's + run, that's a somewhat expensive operation and the data seldom + changes. Can I put jar-query in a mode where after it builds it's + database it sits waiting for me to enter individual queries? + +A: Yes. Use the -I (--interactive) argument. After the database is + built it will prompt you on the command line for a class to + query. You may use TAB to auto-complete the class name. Each TAB + will complete up to the next dot (.) in the class path. + +Tutorial examples of how to use these tools: +-------------------------------------------- + +Let's say we have Java application and need to know which jars must be +present to satisfy the class loading. Here is how you might tackle +that problem. We'll use the example of pki-core. First we need to +determine the imports used in the source code, java-imports can do +this for us. We might do something like this: + +$ java-imports \ + -x /test/ \ + -r \ + ~/src/dogtag/pki/base/setup \ + ~/src/dogtag/pki/base/symkey \ + ~/src/dogtag/pki/base/native-tools \ + ~/src/dogtag/pki/base/util \ + ~/src/dogtag/pki/base/java-tools \ + ~/src/dogtag/pki/base/common \ + ~/src/dogtag/pki/base/selinux \ + ~/src/dogtag/pki/base/ca \ + ~/src/dogtag/pki/base/silent \ + > ~/pkicore-imports + +This instructs java-imports to recursively scan (-r) the set of source +code directories comprising pki-core, but exclude any java file in a +test directory. The result is written to ~/pkicore-imports and we'll +show you a partial snippet below: + +com.netscape.certsrv.* +com.netscape.certsrv.acls.* +com.netscape.certsrv.apps.* +com.netscape.certsrv.apps.CMS +com.netscape.certsrv.authentication.* +com.netscape.certsrv.authentication.AuthCredentials +com.netscape.certsrv.authentication.AuthToken + +Now we want to know which jars and RPM's provide those classes, +jar-query will help do this. Let's develop a strategy. As a first cut +we could do this: + +$ jar-query -d /usr/share/java/pki `cat ~/pkicore-imports` + +This adds the pki specific jar directory to the jar search path and +performs a query for every import statement we located earlier. Looking +at the output we see some immediate problems, there are more than one +jar providing some of the classes, which one do we want? + +If we add the -m argument that will list only classes which have +multiple definitions and which jars they occur in. + +$ jar-query -m -d /usr/share/java/pki `cat ~/pkicore-imports` + +Some examples might be: + +org.w3c.dom.Document + /usr/share/java/libgcj-4.4.4.jar + /usr/share/java/xml-commons-apis-1.4.01.jar + +com.ibm.wsdl.util.StringUtils + /usr/share/java/qname-1.5.2.jar + /usr/share/java/wsdl4j-1.5.2.jar + +junit.framework.TestCase + /usr/share/java/junit-3.8.2.jar + /usr/share/java/junit4-4.6.jar + +O.K. so we run jar-query again and ask it to compare the class +implementations for the duplicates using the -M argument + +$ jar-query -M -d /usr/share/java/pki `cat ~/pkicore-imports` + +For the above examples this is what it reports: + +comparing org.w3c.dom.Document + equal /usr/share/java/libgcj-4.4.4.jar /usr/share/java/xml-commons-apis-1.4.01.jar + +comparing com.ibm.wsdl.util.StringUtils + equal /usr/share/java/qname-1.5.2.jar /usr/share/java/wsdl4j-1.5.2.jar + +comparing junit.framework.TestCase + not equal /usr/share/java/junit-3.8.2.jar /usr/share/java/junit4-4.6.jar + +One thing to notice is that libgcj appears frequently in the duplicate +list and is somewhat of a kitchen sink providing copies of many +classes. We never explicitly include libgcj directly anyway. So let's +exclude libgcj from consideration by providing this argument to +jar-query: -x libgcj the next time. + +qname-1.5.2.jar and wsdl4j-1.5.2.jar both provide copies of the same +classes, they both have the same version number, thus we can conclude +they are synonyms for one another and we should pick which we'll use. + +Ah, but junit-3.8.2.jar and junit4-4.6.jar are not providing the same +implementation of the class and we notice they have different versions +embedded in their jar names. Thus we can conclude multiple versions of +a jar have been installed and we must be careful to pick the jar whose +version matches our needs. + +O.K. So armed with this knowledge lets try it again, we'll exclude the +libgcj jar (-x libgcj) and ask for RPM information (-r) and summary +information (-s): + +$ jar-query -s -r -d /usr/share/java/pki -x libgcj `cat ~/pkicore-imports` + +The summary is listed below: + +Summary: +21 Unique Jar's + /usr/lib/jss/jss4-4.2.6.jar + /usr/share/java/commons-codec.jar + /usr/lib/symkey/symkey-9.0.0.jar + /usr/share/java/jakarta-taglibs-core-1.1.1.jar + /usr/share/java/ldapbeans-4.18.jar + /usr/share/java/ldapfilt-4.18.jar + /usr/share/java/ldapjdk-4.18.jar + /usr/share/java/pki-console-2.0.0.jar + /usr/share/java/pki/certsrv-9.0.0.jar + /usr/share/java/pki/cms-9.0.0.jar + /usr/share/java/pki/cmscore-9.0.0.jar + /usr/share/java/pki/cmsutil-9.0.0.jar + /usr/share/java/pki/nsutil-9.0.0.jar + /usr/share/java/tomcat5-jsp-2.0-api-5.5.27.jar + /usr/share/java/tomcat5-servlet-2.4-api-5.5.27.jar + /usr/share/java/tomcat6-jsp-2.1-api-6.0.26.jar + /usr/share/java/tomcat6-servlet-2.5-api-6.0.26.jar + /usr/share/java/velocity-1.6.3.jar + /usr/share/java/xerces-j2-2.9.0.jar + /usr/share/java/xml-commons-apis-1.4.01.jar + /usr/share/java/xml-commons-apis-ext-1.4.01.jar +15 Unique RPM's + jakarta-taglibs-standard + jss + ldapjdk + apache-commons-codec + pki-common + pki-console + pki-symkey + pki-util + tomcat5-jsp-2.0-api + tomcat5-servlet-2.4-api + tomcat6-jsp-2.1-api + tomcat6-servlet-2.5-api + velocity + xerces-j2 + xml-commons-apis + +What this is telling us is that there are 21 jars which provide all +the classes needed to satisfy the import statements. However there may +be some duplicates in the list. Also because of wildcard import +statements some classes and hence jars may have been included which +are not actually utilized in the code. + +Those 21 jars are provided by the 15 RPM's listed. Once again there +may be some class duplication and/or unnecessary RPM's due to +wildcarding. But this gives a very small manageable list to manually +pick through and make our choices. For example, one thing we can +immediately see is that both tomcat5-servlet-2.4-api and +tomcat6-servlet-2.5-api appear in the list, we clearly need only one +version and pick the one for the version of tomcat we're targeting, +hence tomcat6-servlet-2.5-api, same applies to tomcat6-jsp-2.1-api. + diff --git a/tools/jar/jar-query b/tools/jar/jar-query new file mode 100755 index 000000000..5f0e081cd --- /dev/null +++ b/tools/jar/jar-query @@ -0,0 +1,542 @@ +#!/usr/bin/python + +# Copyright John Dennis, jdennis@redhat.com + +import getopt +import zipfile +import re +import fnmatch +import os +import sys +import readline +try: + import rpm + have_rpm = True +except: + have_rpm = False + + +#------------------------------------------------------------------------------- + +prog_name = os.path.basename(sys.argv[0]) + +prompt = "Enter class: " +std_java_dir = "/usr/share/java" +std_jni_dir = "/usr/lib/java" +path_to_class_re = re.compile('/') +class_to_path_re = re.compile('\.') +rpm_file_cache = {} +exclude_jar_patterns = [] +exclude_jar_regexps = [] + +config = { + 'jar-dirs' : [std_java_dir, std_jni_dir], + 'action' : 'query', + 'interactive' : False, + 'ignore-case' : False, + 'pattern-glob' : True, + 'show-rpms' : False, + 'summary' : False, +} + +#------------------------------------------------------------------------------- + + +def get_rpm_name_by_file_path(path): + if path is None: + return None + + name = None + try: + ts = rpm.ts() + mi = ts.dbMatch(rpm.RPMTAG_BASENAMES, path) + for h in mi: + name = h['name'] + break + except: + print sys.stderr >> "ERROR: failed to retrieve rpm info for %s" % path + return name + +def get_rpm_name(path): + if rpm_file_cache.has_key(path): + return rpm_file_cache[path] + + rpm_name = get_rpm_name_by_file_path(path) + if rpm_name is None: + return None + rpm_file_cache[path] = rpm_name + return rpm_name + +#------------------------------------------------------------------------------- + +def get_jar_classes(jar_path): + class_list = [] + + try: + f = zipfile.ZipFile(jar_path) + except IOError, e: + print "Error: %s (%s)" % (e.filename, e.strerror) + return class_list + except Exception, e: + print "Error: %s" % e + return class_list + + # For every entry in the zip file determine if it is a .class file, + # if so translate / to . and add it to the class list + for name in f.namelist(): + path, ext = os.path.splitext(name) + if ext == ".class": + path = path_to_class_re.sub('.', path) + class_list.append(path) + + return class_list + +def get_files_in_dir(dir_path, recursive=False): + paths = [] + + if recursive: + # Walk the directory + for dir_path, dir_names, file_names in os.walk(dir_path, followlinks=True): + # for every non-directory determine if it's a plain file or a symlink + for name in file_names: + path = os.path.join(dir_path, name) + paths.append(path) + else: + names = os.listdir(dir_path) + for name in names: + path = os.path.join(dir_path, name) + if os.path.isdir(path): + continue + paths.append(path) + return paths + +def get_jars(paths, jar_paths): + ''' + Iterate over all the paths. If the path is a link then traverse + each link until it resolves to a file (e.g. the jar_path), record + the traversal path used to reach the jar_path in a list. + + If the resolved path is a zip file record it the jar_paths dict, + this assures only a single path for the jar is recorded because + there may be many ways to reach it via symbolic links. + + The value in the jar_paths dict for the jar_path is a list of + traversal lists. Thus if the jar_path was never reached via + symbolic list traversal it will have an empty list. If the + jar_path was reached by one traversal the list will contain one + list. If the jar_path was reached by two different traversals the + list will contain two lists, etc. + ''' + # for every non-directory determine if it's a plain file or a symlink + for path in paths: + links = None + if os.path.islink(path): + # The file is a symlink, chase down the links until it resolves to a real file + links = [] + tmp_path = path + while os.path.islink(tmp_path): + links.append(tmp_path) + target = os.readlink(tmp_path) + #print "link %s -> %s" % (tmp_path, target) + if target.startswith("/"): + tmp_path = target + else: + tmp_path = os.path.normpath(os.path.join(os.path.dirname(tmp_path), target)) + links.append(tmp_path) + # jar_path is a real file worthy of consideration + jar_path = tmp_path + #print jar_path + elif os.path.isfile(path): + # It's a real file, consider it + #print path + jar_path = path + + # Is the path a Zip file, if so consider it a jar and add it to the jar_paths + if zipfile.is_zipfile(jar_path): + jar_links = jar_paths.setdefault(jar_path, []) + if links: + jar_links.append(links) + +def is_jar_excluded(jar_path): + for regexp in exclude_jar_regexps: + if regexp.search(jar_path): + return True + return False + + +#------------------------------------------------------------------------------- + +class JarCollection: + def __init__(self, jar_dirs=None): + self.jar_dirs = jar_dirs + self.jar_pathnames = None + self.class_jar_paths = {} + self.classes = None + self.all_jars = {} + self.all_rpms = {} + + # Get a list of all UNIQUE jar path names + self.jar_paths = {} + for jar_dir in self.jar_dirs: + paths = get_files_in_dir(jar_dir) + get_jars(paths, self.jar_paths) + + # self.jar_paths is a dict, each key is one jar file. The + # value of the key is a list of traversal lists. If the + # jar_path was not reached by symbolic links the list will be + # empty. If the jar path could be reached by one or more + # traversal paths then there will be a list of each traveral + # path. Each traversal path list is a sequence starting with + # the original path (which by definition is a symbolic link) + # followed by every intermediate symbolic link ending with the + # jar_path. + # + # Thus the keys in self.jar_paths represent the UNIQUE jar + # files and the value of the key provides all the ways that + # jar file can be reached via symbolic links. + + self.jar_pathnames = self.jar_paths.keys() + self.jar_pathnames.sort() + + # For each unique jar get it's classes and add it to the table of class jar_paths + for jar_path in self.jar_pathnames: + if is_jar_excluded(jar_path): + continue + for klass in get_jar_classes(jar_path): + class_jar_path_list = self.class_jar_paths.setdefault(klass, []) + class_jar_path_list.append(jar_path) + + self.classes = self.class_jar_paths.keys() + self.classes.sort() + + def show_class_jar_paths(self, classes=None): + if classes is None: classes = self.classes + for klass in classes: + print "%s" % klass + jar_paths = self.class_jar_paths[klass] + for jar_path in jar_paths: + self.all_jars[jar_path] = True + if config['show-rpms']: + rpm_name = get_rpm_name(jar_path) + self.all_rpms[rpm_name] = True + print " %s [rpm: %s]" % (jar_path, rpm_name) + else: + print " %s" % jar_path + + if config['summary']: + print + print "Summary:" + print "%d Unique Jar's" % (len(self.all_jars)) + jar_paths = self.all_jars.keys() + jar_paths.sort() + for jar_path in jar_paths: + print " %s" % jar_path + + if config['show-rpms']: + print "%d Unique RPM's" % (len(self.all_rpms)) + rpm_names = self.all_rpms.keys() + rpm_names.sort() + for rpm_name in rpm_names: + print " %s" % rpm_name + + + def show_jar_links(self): + for jar_path in self.jar_pathnames: + jar_links = self.jar_paths[jar_path] + if len(jar_links) == 0: continue + print "%s" % jar_path + for link_traversal in jar_links: + print " %s" % ' -> '.join(link_traversal) + + def lookup_class(self, klass): + return self.class_jar_paths.get(klass, None) + + def search_class(self, pattern): + classes = [] + regexp_flags = 0 + + if config['pattern-glob']: + pattern = '^' + fnmatch.translate(pattern) + + if config['ignore-case']: regexp_flags |= re.IGNORECASE + + try: + regexp = re.compile(pattern, regexp_flags) + except Exception, e: + print >>sys.stderr, "ERROR, cannot compile search pattern '%s' (%s)" % (pattern, e) + return None + + for klass in self.classes: + if regexp.search(klass): + classes.append(klass) + + return classes + + def find_multiple_definitions(self): + classes = [] + + for klass in self.classes: + if len(self.class_jar_paths[klass]) > 1: + classes.append(klass) + + return classes + + def compare_multiple_definitions(self, classes): + n_defs = 0 + n_equal = 0 + n_not_equal = 0 + + for klass in classes: + print "comparing %s" % klass + jar_paths = self.class_jar_paths[klass] + n_jar_paths = len(jar_paths) + name = class_to_path_re.sub('/', klass) + name += '.class' + + class_defs = [] + for jar_path in jar_paths: + n_defs += 1 + f = zipfile.ZipFile(jar_path) + class_def = f.read(name) + class_defs.append(class_def) + f.close() + + i = 0 + while i < n_jar_paths-1: + class_def1 = class_defs[i] + j = i + 1 + while j < n_jar_paths: + class_def2 = class_defs[j] + + result = cmp(class_def1, class_def2) + if result == 0: + result_str = " equal" + n_equal += 1 + else: + result_str = "not equal" + n_not_equal += 1 + + print " %s %s %s" % (result_str, jar_paths[i], jar_paths[j]) + j += 1 + i += 1 + + print "%d classes, %d multiple classes, %d class defintions, %d equal, %d not_equal" % \ + (len(self.classes), len(classes), n_defs, n_equal, n_not_equal) + +#------------------------------------------------------------------------------- +class Completer: + def __init__(self, classes): + self.completion_index = 0 + self.completions = {} + + for klass in classes: + table = self.completions + components = klass.split(".") + for component in components: + table = table.setdefault(component, {}) + + #print self.completions + + def do_complete(self, text, state): + try: + # State will be 0 when a new completion begins, + # otherwise it will be the index of the next possible completion. + # We signal the end of possible completions by returning None. + if state == 0: + i = 0 + else: + i = self.completion_index + 1 + + #print "text=%s state=%s" % (text, state) + + # Find out which word we're completing and get the table for that word + leading_text = readline.get_line_buffer()[0 : readline.get_begidx()] + #print "leading_text=%s" % leading_text + words = leading_text.split(".") + table = self.completions + for word in words: + if len(word): + table = table.get(word) + if table is None: return None + + completions = table.keys() + completions.sort() + + #print "completions=%s" % completions + while i < len(completions): + if completions[i].startswith(text): + self.completion_index = i + return completions[i] + i += 1 + return None + except Exception, e: + print "Completer Exception: %s" % (e) + + +#------------------------------------------------------------------------------- + +class Usage(Exception): + def __init__(self, msg): + self.msg = msg + +def usage(): + 'Command help.' + + return '''\ + +%(prog_name)s [pattern ...] + +-h --help print help +-d --dir add dir to jar search path +-D --clear-dirs clear the jar search path +-l --list list every class and the jar's it's located in +-i --ignore-case when searching with regular expressions ignore case +-R --regexp when searching use full regular expressions (default is globbing) +-I --interactive keep prompting for a class (with tab completion) +-m --multiple list classes which have multiple definitions +-M --multiple-compare for each multiply defined class show if they are equal +-r --show-rpms when listing jars show which rpms provides it +-L --links list each jar symbolic link traversal +-x --exclude exclude any jar file matching this + regular expression. May be specified multiple times. +-s --summary dump summary information + +Standard Jar Search Path = %(jar_path)s + +In interactive mode tab will complete up to the next location in the +class hierarchy. Then enter a dot (.) to move to the next location in +the class hierarchy, tab will complete again, repeat until the class +name is fully specified. (Note, after a fully completing a name a +space is inserted which you'll have to backspace over, sorry this is +limitation of the Python completion implementation). + +Examples: + +# Interactive class lookup with tab completion +%(prog_name)s -I + +# Find all classes matching regular expression pattern +%(prog_name)s pattern + +# Add a directory to jar search path, interactively lookup classes +%(prog_name)s -d /usr/share/foo/java/lib -I + +# Search only this directory and search for pattern +%(prog_name)s -D -d /usr/share/foo/java/lib pattern +''' % {'prog_name' : prog_name, + 'jar_path' : config['jar-dirs'], + } + + +#------------------------------------------------------------------------------- + +def main(argv=None): + if argv is None: + argv = sys.argv + + try: + try: + opts, args = getopt.getopt(argv[1:], 'hd:DlIiRmMrLx:s', + ['help', 'dir=', 'clear-dirs', 'list', + 'interactive', 'ignore-case', 'regexp', 'multiple', + 'multiple-compare', 'show-rpms', 'links', + 'exclude=', 'summary']) + except getopt.GetoptError, err: + print >>sys.stderr, str(err) # will print something like 'option -a not recognized' + usage() + return 2 + + for o, a in opts: + if o in ('-h', '--help'): + print >>sys.stdout, usage() + return 0 + elif o in ('-l', '--list'): + config['action'] = 'list' + elif o in ('-m', '--multiple'): + config['action'] = 'list-multiple' + elif o in ('-M', '--multiple-compare'): + config['action'] = 'compare-multiple' + elif o in ('-d', '--dir'): + config['jar-dirs'].append(a) + elif o in ('-D', '--clear-dirs'): + config['jar-dirs'] = [] + elif o in ('-i', '--ignore-case'): + config['ignore-case'] = True + elif o in ('-R', '--regexp'): + config['pattern-glob'] = False + elif o in ('-I', '--interactive'): + config['interactive'] = True + elif o in ('-r', '--show-rpms'): + if have_rpm: + config['show-rpms'] = True + else: + print >>sys.stderr, "ERROR: python rpm module not available, cannot enable RPM reporting" + return 1 + elif o in ('-L', '--links'): + config['action'] = 'list-links' + elif o in ('-x', '--exclude'): + exclude_jar_patterns.append(a) + elif o in ('-s', '--summary'): + config['summary'] = True + else: + raise Usage("command argument '%s' not handled, internal error" % o) + except Usage, e: + print >>sys.stderr, e.msg + print >>sys.stderr, "for help use --help" + return 2 + + + for pat in exclude_jar_patterns: + try: + regexp = re.compile(pat) + exclude_jar_regexps.append(regexp) + except Exception, e: + print >>sys.stderr, "ERROR, cannot compile exclude pattern '%s' (%s)" % (pat, e) + return 1 + + jc = JarCollection(config['jar-dirs']) + + # Database is now built, determine how we want to query it + if config['action'] == 'list': + jc.show_class_jar_paths() + return 0 + elif config['action'] == 'list-multiple': + classes = jc.find_multiple_definitions() + jc.show_class_jar_paths(classes) + return 0 + elif config['action'] == 'compare-multiple': + classes = jc.find_multiple_definitions() + jc.show_class_jar_paths(classes) + jc.compare_multiple_definitions(classes) + return 0 + elif config['action'] == 'list-links': + jc.show_jar_links() + return 0 + + unique_classes = {} + for pattern in args: + for klass in jc.search_class(pattern): + unique_classes[klass] = True + if len(unique_classes): + classes = unique_classes.keys() + classes.sort() + jc.show_class_jar_paths(classes) + + if config['interactive']: + completer = Completer(jc.classes) + readline.set_completer_delims(".") + readline.set_completer(completer.do_complete) + readline.parse_and_bind("tab: complete") + + while True: + try: + input = raw_input(prompt).strip() + except EOFError: + break + print jc.lookup_class(input) + return 0 + +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/jar/java-imports b/tools/jar/java-imports new file mode 100755 index 000000000..44c239ca0 --- /dev/null +++ b/tools/jar/java-imports @@ -0,0 +1,195 @@ +#!/usr/bin/python + +# Copyright John Dennis, jdennis@redhat.com + +import getopt +import os +import re +import sys + +#------------------------------------------------------------------------------- + +prog_name = os.path.basename(sys.argv[0]) + +config = { + 'recursive' : False, + 'list-containing' : False, + 'list-files' : False, +} + +block_comment_re = re.compile(r'/\*.*?\*/', re.MULTILINE | re.DOTALL) +line_comment_re = re.compile(r'//.*$', re.MULTILINE) +import_re = re.compile(r'^\s*\bimport\s+([^ \t\n;]+)\s*;', re.MULTILINE) + +exclude_patterns = [] +exclude_regexps = [] + +#------------------------------------------------------------------------------- + +def filter_files(java_files): + filtered_files = [] + + for path in java_files: + filtered = False + for regexp in exclude_regexps: + if regexp.search(path): + filtered = True + break + if not filtered: + filtered_files.append(path) + + return filtered_files + +def find_java_files(path): + java_files = [] + + if os.path.isfile(path): + if path.endswith(".java"): + java_files.append(path) + elif os.path.isdir(path): + if config['recursive']: + for dir, dirs, files in os.walk(path): + for file in files: + if file.endswith(".java"): + pathname = os.path.join(dir,file) + java_files.append(pathname) + else: + print path + for file in os.listdir(path): + print file + if file.endswith(".java"): + pathname = os.path.join(path,file) + java_files.append(pathname) + + return java_files + +def get_imports(pathname, imports): + # Get the text of the file + f = open(pathname) + text = f.read() + f.close() + + # Nuke all comments + text = line_comment_re.sub('', text) + text = block_comment_re.sub('', text) + + for match in import_re.finditer(text): + import_string = match.group(1) + import_locations = imports.setdefault(import_string, {}) + import_locations[pathname] = None + +#------------------------------------------------------------------------------- + +class Usage(Exception): + def __init__(self, msg): + self.msg = msg + +def usage(): + 'Command help.' + + return '''\ + +%(prog_name)s path [path ...] + +-h --help print help +-c --list-containing show source file where each import statement occurs +-r --recursive scan directory contents recursively +-l --list-files list the java files found +-x --exclude exclude from the file list any file matching this + regular expression. May be specified multiple times. + +Examples: + +# Find all imports in directory ~/src/myproject/subsystem +%(prog_name)s ~/src/myproject/subsytem + +# Find all imports in and below ~/src/myproject +%(prog_name)s -r ~/src/myproject + +# Find all imports in and below ~/src/myproject +# excluding any in a test directory +%(prog_name)s -x /test/ -r ~/src/myproject +''' % {'prog_name' : prog_name, + } + +#------------------------------------------------------------------------------- + +def main(argv=None): + if argv is None: + argv = sys.argv + + try: + try: + opts, args = getopt.getopt(argv[1:], 'hrclx:', + ['help', 'recursive', + 'list-containing', 'list-files', + 'exclude=']) + except getopt.GetoptError, err: + print >>sys.stderr, str(err) # will print something like 'option -a not recognized' + usage() + return 2 + + for o, a in opts: + if o in ('-h', '--help'): + print >>sys.stdout, usage() + return 0 + elif o in ('-r', '--recursive'): + config['recursive'] = True + elif o in ('-c', '--list-containing'): + config['list-containing'] = True + elif o in ('-l', '--list-files'): + config['list-files'] = True + elif o in ('-x', '--exclude'): + exclude_patterns.append(a) + else: + raise Usage("command argument '%s' not handled, internal error" % o) + except Usage, e: + print >>sys.stderr, e.msg + print >>sys.stderr, "for help use --help" + return 2 + + for pat in exclude_patterns: + try: + regexp = re.compile(pat) + exclude_regexps.append(regexp) + except Exception, e: + print >>sys.stderr, "ERROR, cannot compile exclude pattern '%s' (%s)" % (pat, e) + return 1 + + java_files = [] + for path in args: + java_files.extend(find_java_files(path)) + + java_files = filter_files(java_files) + + if config['list-files']: + print "\n".join(java_files) + return 0 + + imports = {} + for java_file in java_files: + get_imports(java_file, imports) + + import_strings = imports.keys() + import_strings.sort() + + if not config['list-containing']: + print "\n".join(import_strings) + else: + for import_string in import_strings: + print import_string + locations = imports[import_string].keys() + locations.sort() + for location in locations: + print " %s" % location + + return 0 +#------------------------------------------------------------------------------- + +if __name__ == '__main__': + sys.exit(main()) + + + + + |