/*
* ALMA - Atacama Large Millimiter Array
* (c) European Southern Observatory, 2004
* Copyright by ESO (in the framework of the ALMA collaboration),
* All rights reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
package alma.acs.eclipse.utils.jar;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import alma.acs.makesupport.AcsJarFileFinder;
/**
* Objects from this class allow to browse the content of a jar file focusing
* on classes and packages.
* <P>
* There are 2 ways to use <code>JarFileHelper</code> objects:
* <OL>
* <LI>from the name of a jar like for example <code>lc.jar</code>: in this case
* the jar file is retrieved by following ACS rules i.e. ../lib, INROOT/lib...
* <LI>by passing directly the jar file like for example
* <code>$INTROOT/lib/lc.jar</code>
* </OL>
* <P>
* Given the name of a jar file, <code>JarFile</code> get the right jar file from the
* file system looking the folder as ACS rules.
* The content of thSe jar file is then examined to get the list of the java classes and packages
* it contains as well as any other info needed.
* <P>
* <b>Note</b>: this class is intended to work only with ACS distributed jar files.
*
* @author acaproni
*
*/
public class JarFileHelper {
/**
* When adding a package in {@link JarFileHelper#addJavaPackage(String, Collection)} a package
* is added to the vector only if it was not already there.
*
* We need to know in the returned value of that method if what happened with a package
* because we want to send a log if a jar file does not contain any package because it be an error.
* <i>NOTE</i> that it is different to say that a jar does not contain any package (an error) or that the package
* has not been added to the vector because already present (not an error).
*
* @author acaproni
*
*/
private enum AddPackageReturnType {
NO_PACKAGE,
PKG_ALREADY_IN_VECTOR,
PKG_ADDED
};
/**
* The filter is used by {@link AcsJarFileFinder} to look for more then one
* jar in the same time.
* <P>
* In our case the filter will allow accessing only the file whose name
* is the name of the jar.
*
* @author acaproni
*
*/
class JarFileNameFilter implements FilenameFilter {
/**
* The name of the jar file to look for
* (for example <code>lc.jar</code>)
*/
private final String name;
/**
* Constructor
*
* @param name The name of the jar file
*/
public JarFileNameFilter(String name) {
if (name==null || name.isEmpty()) {
throw new IllegalArgumentException("Invalid jar file name file");
}
if (!name.toLowerCase().endsWith(".jar")) {
System.out.println("warning: suspicious name of a jar file "+name);
}
this.name=name;
}
/**
* @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
*/
@Override
public boolean accept(File dir, String name) {
return name.equals(this.name);
}
}
/**
* The name of the jar file like for example <code>lc.jar</code>
*/
private final String name;
/**
* The jar file.
* <P>
* This file is obtained by scanning ACS folder like INTROOT, ACSROOT
* and so on
*/
private final File file;
/**
* The logger
*/
private static final Logger logger = Logger.getAnonymousLogger();
/**
* Constructor.
*
* @param name The name of the ACS jar file (like <code>lc.jar</code>)
* @param folders The folders to look for the jar files
* @throws Exception
*/
public JarFileHelper(String name, String[] folders) throws Exception {
if (name==null || name.isEmpty()) {
throw new IllegalArgumentException("Invalid jar file name");
}
if (folders==null || folders.length==0) {
throw new IllegalArgumentException("Invalid folders");
}
this.name=name;
File[] dirs = new File[folders.length];
for (int t=0; t<folders.length; t++) {
dirs[t] = new File(folders[t]);
if (!dirs[t].canRead()) {
throw new Exception("Unreadable folder "+folders[t]);
}
}
AcsJarFileFinder jarFileFinder= new AcsJarFileFinder(dirs,logger);
File[] files = jarFileFinder.getFiles(new JarFileNameFilter(name));
if (files==null || files.length==0) {
throw new Exception(name+" not found");
}
if (files.length>1) {
System.out.println("Warning; found more then one file for "+name);
for (File f: files) {
System.out.println("\t"+f.getAbsolutePath());
}
System.out.println("\tusing the first one: "+files[0]);
}
file=files[0];
// Ensure the jar is readable
if (!file.canRead()) {
throw new Exception(file.getAbsolutePath()+" unreadable");
}
}
/**
* Constructor
*
* @param file The jar file
*/
public JarFileHelper(File file) throws Exception {
if (file==null || !file.canRead()) {
throw new IllegalArgumentException("Invalid/unreadable file");
}
this.file=file;
this.name=file.getName();
}
/**
* Flush the java packages contained in this jar file
* into the passed vector.
*
* @return the number of java packages in the jar file
* @throws IOException In case of error reading the jar file
*/
public int getPackages(Collection<String> javaPackages) throws IOException {
if (javaPackages==null) {
throw new IllegalArgumentException("The vector can't be null");
}
AddPackageReturnType ret;
int sz=0;
JarFile jar = new JarFile(file);
Enumeration<JarEntry> entries = jar.entries();
for (Enumeration<JarEntry> em1 = entries; em1.hasMoreElements();) {
String str=em1.nextElement().toString().trim();
ret=addJavaPackage(str,javaPackages);
if (ret!=AddPackageReturnType.NO_PACKAGE) {
sz++;
}
}
jar.close();
return sz;
}
/**
* Add a java package to the vector if it is not already there.
* <P>
* The package name is calculated by checking the passed string
*
* @param str The entry of the jar that could point to a jar
* @param vector The vector of packages where the package must be added
* @return <code>true</code> if the package has been added to the vector;
* <code>false</code> otherwise
*
*/
private AddPackageReturnType addJavaPackage(String str, Collection<String> vector) {
if (vector==null) {
throw new IllegalArgumentException("The vector can't be null");
}
if (str==null || str.isEmpty()) {
return AddPackageReturnType.NO_PACKAGE;
}
if (!str.toLowerCase().endsWith(".class")) {
return AddPackageReturnType.NO_PACKAGE;
}
int pos = str.lastIndexOf("/");
if (pos>0) {
str=str.substring(0,pos);
}
str=str.replaceAll("/", ".");
if (!vector.contains(str)) {
vector.add(str);
return AddPackageReturnType.PKG_ADDED;
} else {
return AddPackageReturnType.PKG_ALREADY_IN_VECTOR;
}
}
/**
* Flush the java classes contained in this jar file
* into the passed vector.
*
* @return The java classes in the jar file
*/
public void getClasses(Collection<String> javaClasses) throws IOException {
if (javaClasses==null) {
throw new IllegalArgumentException("The vector can't be null");
}
JarFile jar = new JarFile(file);
Enumeration<JarEntry> entries = jar.entries();
for (Enumeration<JarEntry> em1 = entries; em1.hasMoreElements();) {
String str=em1.nextElement().toString().trim();
addJavaClass(str,javaClasses);
}
jar.close();
}
/**
* Check if a class with the given name is in the jar.
* The match is not done for the whole name of the class
* but from its initial part.
* For example if the parameter is Logging and the jar is <code>lc.jar</code> then
* the returned array contains LoggingClient and LoggingClientText
*
* @param name The beginning of the name of the class
* @return A list of class names matching the parameter or <code>null</code>
* if no class name in the jar matches with the parameter
* @throws IOException In case of error reading the jar file
*/
public Collection<String> getMatchingClasses(String name) throws IOException {
// All the java classes in the jar
Vector<String> classes = new Vector<String>();
Vector<String> matchingClassesVector=null;
getClasses(classes);
for (String javaClass: classes) {
// javaClass can be com.cosylab.logging.LoggingClient.class
String[] parts = javaClass.split("\\.");
if (parts.length<2) {
continue;
}
if (parts[parts.length-2].startsWith(name)) {
if (matchingClassesVector==null) {
matchingClassesVector= new Vector<String>();
}
matchingClassesVector.add(javaClass);
}
}
return matchingClassesVector;
}
/**
* Add a java class to the vector if it is not already there.
* <P>
* The class name is calculated by checking the passed string.
*
* @param str The entry of the jar that could point to a java class
* @param vector The vector of classes where the package must be added
*
*/
private void addJavaClass(String str, Collection<String> vector) {
if (vector==null) {
throw new IllegalArgumentException("The vector can't be null");
}
if (str==null || str.isEmpty()) {
return;
}
if (!str.toLowerCase().endsWith(".class")) {
return;
}
str=str.replaceAll("/", ".");
if (!vector.contains(str)) {
vector.add(str);
}
}
/**
* @return the absolute path of the file of the jar
*/
public String getFilePathName() {
return file.getAbsolutePath();
}
/**
* @return the name
*/
public String getName() {
return name;
}
}