Frustrated by all the time wasted expanding and repacking J2EE enterprise
application archives (ear files) looking for bean classes and the like,
I wrote this small tool for searching an archive recursively for files
whose paths match a regular expression.
Here's a sample use:
$ java -jar jarsearch.jar -a CMRouter New_Project.ear
Searching for 'CMRouter' ...
There are 3 matches:
New_Project.ear>New_Project.jar>frameworkImpl/model/router/CMRouterBean.class
New_Project.ear>New_Project.jar>frameworkImpl/model/router/CMRouterLocal.class
New_Project.ear>New_Project.jar>frameworkImpl/model/router/CMRouterLocalHome.class
The Jar Search utility consists of one class:
packagenet.das.jarsearch;importjava.io.File;importjava.io.FileFilter;importjava.io.IOException;importjava.util.ArrayList;importjava.util.Enumeration;importjava.util.Iterator;importjava.util.List;importjava.util.Set;importjava.util.TreeSet;importjava.util.jar.JarEntry;importjava.util.jar.JarFile;importjava.util.jar.JarInputStream;importgnu.regexp.RE;importgnu.regexp.REException;/**
* Searches a set of directories and archives for files whose names match a given
* regular expression. The search may optionally recurse directories or archives.
*
* @author <a href="doug@dseifert.net">Doug Seifert</a>
*/publicclassJarSearch{privateSetmInitialFiles;privateSetmFiles;privateREmSearchRE;privatebooleanmRecurseDirectories=false;privatebooleanmRecurseArchives=false;/**
* Create a search object that will search a set of directories, files and archives
* for files whose path names match the provided regular expression. The list
* may contain files, in which case the search is made against the file itelf. The
* list may also contain directories, in which case the directory and optionally all
* it's contents are searched. Finally, the list may contain jar archives. By default,
* only archive contents are searched. Optionally, archives within archives (archive
* recursion) may be searched.
*
* @param aSearchTerm A string search expression that is turned into a
* gnu.regexp.RE object by invoking the RE(Object) constructor.
* @param lDirectories The set of files, directories and archives to search.
* @throws REException If the search term can't be turned into a valid RE object.
*/publicJarSearch(StringaSearchTerm,SetlDirectories)throwsREException{mSearchRE=newRE(aSearchTerm);mInitialFiles=lDirectories;}/**
* Create a search object that will search a set of directories, files and archives
* for files whose path names match the provided regular expression. The list
* may contain files, in which case the search is made against the file itelf. The
* list may also contain directories, in which case the directory and optionally all
* it's contents are searched. Finally, the list may contain jar archives. By default,
* only archive contents are searched. Optionally, archives within archives (archive
* recursion) may be searched.
*
* @param aSearchRE A gnu.regexp.RE used to match file path names against.
* @param lDirectories The set of files, directories and archives to search.
*/publicJarSearch(REaSearchRE,SetlDirectories){mSearchRE=aSearchRE;mInitialFiles=lDirectories;}/**
* The main program for the JarSearch class. This program may be invoked as follows:
* <code>java net.das.jarsearch.JarSearch [-r] [-a] {regexpstring} [file ...]</code>
* <ul>
* <li>If the -r flag is provided, any directories provided as arguments will be recursed.</li>
* <li>If the -a flag is provided, any archives provided as arguments, or any archives found
* as a result of directory recursion, will be recursed</li>
* </ul>
*
* <p>
* Archive recursion means that archives nested with archives to any level will be searched.
* </p>
* <p>
* This method will call System.exit(0) if the search was performed without an error. If an
* error occurs (for example, because the regexpstring can't be parsed into a valide
* gnu.regexp.RE object), System.exit(1) will be called. System.exit(2)
* will be called in the event of a usage error.
* </p>
* <p>
* Example:<br>
* java net.das.jarsearch.JarSearch -a Bean foo.ear<br>
* Will list all files whose names contain the substring 'Bean' in the given ear archive.
* Module archives that might be contained in the ear will also be searched.
* </p>
*
* @param args The command line arguments
*/publicstaticvoidmain(String[]args){if(args.length<2){System.err.println("Usage: java "+JarSearch.class+" [-r] [-a] <regexpstring> [file ...]");System.err.println(" <regexpstring> is a string that can be parsed into a "+"valid gnu.regexp.RE object.");System.err.println(" zero or more directories may be specified. If none are specified,"+" the current working directory is searched.");System.exit(2);}try{intlArgIndex=0;booleanlRecurseDirs=false;booleanlRecurseArchives=false;while(args[lArgIndex].startsWith("-")){if("-a".equals(args[lArgIndex])){lRecurseArchives=true;}elseif("-r".equals(args[lArgIndex])){System.out.println("Arg is -r, recurse dirs = true");lRecurseDirs=true;}lArgIndex++;}if(args.length-lArgIndex<2){System.err.println("Usage: java "+JarSearch.class+" [-r] [-a] <regexpstring> [file ...]");System.err.println(" <regexpstring> is a string that can be parsed into a "+"valid gnu.regexp.RE object.");System.err.println(" zero or more directories may be specified. If none are specified,"+" the current working directory is searched.");System.exit(2);}StringlSearchTerm=args[lArgIndex++];SetlDirectories=newTreeSet();for(inti=lArgIndex;i<args.length;++i){lDirectories.add(newFile(args[i]));}JarSearchlSearch=newJarSearch(lSearchTerm,lDirectories);lSearch.setRecurseArchives(lRecurseArchives);lSearch.setRecurseDirectories(lRecurseDirs);System.out.println("Searching for '"+lSearchTerm+"' ...");ListlResults=lSearch.execute();System.out.println("There are "+lResults.size()+" matches:");Iteratori=lResults.iterator();while(i.hasNext()){StringlMatch=(String)i.next();System.out.println(lMatch);}}catch(Exceptione){e.printStackTrace();System.exit(1);}System.exit(0);}/**
* Flag the search to recurse directories.
*
* @param aFlag Turn on or off directory recursion.
*/publicvoidsetRecurseDirectories(booleanaFlag){mRecurseDirectories=aFlag;}/**
* Flag the search to recurse archives. If this is true,
* archives within archives will be searched.
*
* @param aFlag Turn on or off archive recursion.
*/publicvoidsetRecurseArchives(booleanaFlag){mRecurseArchives=aFlag;}/**
* Perform the search and return a List of search results. The result is a list
* of String objects of the form:
* <pre>
* path/that/matched
* path/of/archive/that/matched.jar
* archive.jar>that/has/a/matching/file
* an/archive.jar>within/an/archive.jar>that/has/a/match
* ...
* </pre>
* @return A list of matches of the search expression
*/publicListexecute(){// Find all files first. Returns a list of all command line args// and their children, recursively, if the -r flag was providedmFiles=findAllFiles(mInitialFiles);ListlMatches=newArrayList();Iteratori=mFiles.iterator();while(i.hasNext()){FilelFile=(File)i.next();// Is the file an archive, perform an archive searchif(isArchive(lFile.getName())){try{checkArchiveFile(lFile,lMatches);}catch(IOExceptionioe){System.out.println("Error checking archive: "+ioe);}}else{// It is a regular file, just match the path name against the REif(mSearchRE.getMatch(lFile.getPath())!=null){lMatches.add(lFile.getPath());}}}returnlMatches;}privatevoidcheckArchiveFile(FileaFile,ListaMatches)throwsIOException{JarFilelJar=newJarFile(aFile);try{// Go through the jar entries looking for matchesEnumerationlEntries=lJar.entries();while(lEntries.hasMoreElements()){JarEntrylEntry=(JarEntry)lEntries.nextElement();checkEntry(lJar.getName(),lJar,lEntry,aMatches);}}finally{lJar.close();}}privatevoidcheckArchiveStream(StringaPrefix,JarInputStreamaStream,ListaMatches)throwsIOException{//System.out.println("Checking stream: " + aPrefix);JarEntrylEntry=null;while((lEntry=aStream.getNextJarEntry())!=null){try{checkEntry(aPrefix,null,lEntry,aMatches);if(isArchive(lEntry.getName())&&mRecurseArchives){// We have an archive within an archive, read the data and create a new// Jar input stream for it. We don't want to close this stream, because// it is a substream of a larger open enclosing stream.JarInputStreamlNewStream=newJarInputStream(aStream);checkArchiveStream(aPrefix+">"+lEntry.getName(),lNewStream,aMatches);}else{// Just read and discard the data to get to the next entrybyte[]lBuf=newbyte[4096];while(aStream.read(lBuf,0,4096)>0){// do nothing, throw away the data}}}finally{aStream.closeEntry();}}}privatevoidcheckEntry(StringaPrefix,JarFileaOriginalFile,JarEntryaEntry,ListaMatches)throwsIOException{// If we are looking at an archive within a top-level (on the filesystem) archive,// open a stream and look inside it if the -a flag was specified.if(aOriginalFile!=null&&isArchive(aEntry.getName())&&mRecurseArchives){JarInputStreamlStream=newJarInputStream(aOriginalFile.getInputStream(aEntry));try{checkArchiveStream(aPrefix+">"+aEntry.getName(),lStream,aMatches);}finally{lStream.close();}}else{if(mSearchRE.getMatch(aEntry.getName())!=null){aMatches.add(aPrefix+">"+aEntry.getName());}}}privatebooleanisArchive(StringaName){return(aName.toLowerCase().endsWith(".jar")||aName.toLowerCase().endsWith(".ear")||aName.toLowerCase().endsWith(".zip")||aName.toLowerCase().endsWith(".rar")||aName.toLowerCase().endsWith(".war"));}privateSetfindAllFiles(SetaBaseDirs){// Find all the files in the input set, performs initial directory recursionSetlFiles=newTreeSet();Iteratori=aBaseDirs.iterator();while(i.hasNext()){FilelBaseDir=(File)i.next();if(lBaseDir.isDirectory()&&mRecurseDirectories){findRegularFiles(lBaseDir,lFiles);findArchives(lBaseDir,lFiles);}lFiles.add(lBaseDir);}returnlFiles;}privatevoidfindArchives(FileaDir,finalSetaFiles){File[]lArchiveFiles=aDir.listFiles(newFileFilter(){publicbooleanaccept(Filepathname){if(pathname.isDirectory()){findArchives(pathname,aFiles);returnfalse;}if(isArchive(pathname.getPath())){returntrue;}returnfalse;}});if(lArchiveFiles!=null){for(inti=0;i<lArchiveFiles.length;++i){aFiles.add(lArchiveFiles[i]);}}}privatevoidfindRegularFiles(FileaDir,finalSetaFiles){File[]lRegularFiles=aDir.listFiles(newFileFilter(){publicbooleanaccept(Filepathname){if(pathname.isDirectory()&&mRecurseDirectories){findRegularFiles(pathname,aFiles);returnfalse;}returntrue;}});if(lRegularFiles!=null){for(inti=0;i<lRegularFiles.length;++i){aFiles.add(lRegularFiles[i]);}}}}
To compile it, you will need the the GNU regexp jar, a copy of which can be found in the tarball here:
gzipped tar archive (33K)
The above archive contains an ant based project with
full source. You are welcome to use this code as you see fit.
Just untar the archive and run "ant dist" to produce jarsearch.jar in the project directory.