summaryrefslogtreecommitdiffstats
path: root/tools/jar
diff options
context:
space:
mode:
authorEndi Sukma Dewata <edewata@redhat.com>2012-03-24 02:27:47 -0500
committerEndi Sukma Dewata <edewata@redhat.com>2012-03-26 11:43:54 -0500
commit621d9e5c413e561293d7484b93882d985b3fe15f (patch)
tree638f3d75761c121d9a8fb50b52a12a6686c5ac5c /tools/jar
parent40d3643b8d91886bf210aa27f711731c81a11e49 (diff)
downloadpki-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-tools281
-rwxr-xr-xtools/jar/jar-query542
-rwxr-xr-xtools/jar/java-imports195
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())
+
+
+
+
+