Parse classes from jar using javassist - java

I'm noob in javassist. Anyone can give the sample how to load classes from jar and save them using javassist?
jar = new JarFile(fileName);
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) entries.nextElement();
if(jarEntry == null)
break;
if(jarEntry.getName().endsWith(".class")) {
// ??????
} else {
resources.add(new RResource(jarEntry.getName(), jar.getInputStream(jarEntry)));
}

You can load the bytes from the respective class inside the JAR file via the code below:
JarFile jarFile = new JarFile(file);
// lets get a reference to the .class-file contained in the JAR
ZipEntry zipEntry = jarFile.getEntry(className.replace(".", "/")+".class");
if (zipEntry == null) {
jarFile.close();
return null;
}
// with our valid reference, we are now able to get the bytes out of the jar-archive
InputStream fis = jarFile.getInputStream(zipEntry);
byte[] classBytes = new byte[fis.available()];
fis.read(classBytes);
To load the bytes in javassist you can do the following stuff:
ClassPool cp = ClassPool.getDefault();
cp.insertClassPath(new ClassClassPath(this.getClass()));
ClassPath cp1 = null;
ClassPath cp2 = null;
// add the JAR file to the classpath
try {
cp1 = cp.insertClassPath(jarFile.getAbsolutePath());
} catch (NotFoundException e1) {
e1.printStackTrace();
return null;
}
// add the class file we are going to modify to the classpath
cp2 = cp.appendClassPath(new ByteArrayClassPath(className, classBytes));
byte[] modifiedBytes;
try {
CtClass cc = cp.get(className);
// skip instrumentation if the class is frozen and therefore
// can't be modified
if (!cc.isFrozen()) {
// do your javassist stuff here
}
modifiedBytes = cc.toBytecode();
} catch (NotFoundException | IOException | CannotCompileException | ClassNotFoundException e) {
handleException(e);
} finally {
// free the locked resource files
cp.removeClassPath(cp1);
cp.removeClassPath(cp2);
}
// write your modified bytes somewhere
if (modifiedBytes.length > 0) {
try(FileOutputStream fos = new FileOutputStream("pathname")) {
fos.write(modifiedBytes);
}
}
Maybe some of the code can be reduced, but this is how I load bytes from a JAR file and load them into Javassist. The JAR file is loaded to the Javassist classpath due to eventual dependencies. Also the class which I instrument with Javassist needed to be added to the classpath for some reason.
You might have a look at how I use them in a plugin-use-case:
Loading class-bytes from a JAR file
Javassist instumentation
HTH

Related

Read multiple files zip file with multiple sub directories

I have a zip file with the structure like:
xml.zip
Root Folder: package
Folder: Subfolder.zip
Inside Subfolder.zip :
Root Folder: _
Folder: var
Folder: run
Folder: xmls
Xml1.xml
Xml2.xml
Xml3.xml
Is there a way to read in these three files recursively with the above structure? I've tried using ZipInputStream and ZipArchiveInputStream, but zip.getNextEntry() keeps returning null.. due to the nested zip. Anyway to recursively use it?
private void readZipFileStream(final InputStream zipFileStream) {
final ZipInputStream zipInputStream = new ZipInputStream(zipFileStream);
ZipEntry zipEntry;
try {
zipEntry = zipInputStream.getNextEntry();
while(zipEntry.getName() != null) {
System.out.println("name of zip entry: " + zipEntry.getName());
if (!zipEntry.isDirectory()) {
System.out.println("1."+zipInputStream.available());
System.out.println("2."+zipEntry.getName());
System.out.println("3."+zipEntry.isDirectory());
System.out.println("4."+zipEntry.getSize());
} else {
readZipFileStream(zipInputStream);
}
zipEntry = zipInputStream.getNextEntry();
}
// }
} catch (IOException e) {
e.printStackTrace();
}
}
Can someone please help?
recursive call should be done when the zip entry is identified as zip file. Use zipEntry.getName().endsWith(".zip") to identify and then call the same function.
public static void readZip(InputStream fileStream) throws IOException
{
ZipInputStream zipStream = new ZipInputStream(fileStream);
ZipEntry zipEntry = zipStream.getNextEntry();
try {
while(zipEntry !=null) {
String fileName = zipEntry.getName();
if (fileName.endsWith(".zip")) {
//recur if the entry is a zip file
readZip(zipStream);
}
else {
System.out.println(zipEntry.getName());
}
zipEntry = zipStream.getNextEntry();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Started working after calling the same function again if getName().contains(".zip").

Java JAR files locked by ClassLoader

I am extracting a WAR in a temporary folder and adding all the classes and libs to a URLClassLoader.
When I start to load the classes into the classpath, the JAR files under "WEB-INF/lib/" are locked by the JVM.
After doing the job, I close the classpath, call the GC and delete the temporary directory.
When I am deleting the temporary directory, I can delete all the files except the JAR files under "WEB-INF/lib".
My code:
#Override
public void generateRegressionTestClasses(Path fileJARorWarSolution, List<String> canonicalNameClassesToGenerateTests) throws Exception {
Path tempPath = null;
URLClassLoader classLoader = null;
TryTest tryTest = null;
RDP execution = null;
try {
// This extracts the JAR/WAR and prepares the URLClassLoader.
GenFilterResponse response = genFilterClass.runGenFilterClass(path);
classLoader = response.getCl();
tempPath = response.getTempPath();
// Verify that all the Test classes is in the ClassLoader.
// Here is where the JAR files are locked.
for (String s : canonicalNameClassesToGenerateTests) {
try {
classLoader.loadClass(s);
} catch (ClassNotFoundException e) {
throw new CustomException(CustomTexts.ERROR_CLASS_NOT_LOADED + s);
}
}
execution = new RDP();
execution.setClassLoader(classLoader);
TryTest = new TryTest(execution);
tryTest.handle(null);
} finally {
tryTest = null;
execution = null;
if (classLoader != null) {
try {
classLoader.close();
} catch (Exception e) {
//
} finally {
classLoader = null;
System.gc();
}
}
if (tempPath != null) {
FileSystemUtils.deleteRecursively(tempPath.toFile());
}
}
Anybody knows how to unlock these files?
Solved.
If I load the JAR into the Class Loader with this kind of URI:
new URL("jar:file:" + jar.toAbsolutePath().toString() + "!/").toURI().toURL()
Example URI:
jar:file:c:/myJar.jar
The file is locked after ClassLoader is closed.
If I load the JAR into the Class Loader with this kind of URI:
warDirectory.toFile().toURI().toURL()
Example URI:
file:c:/myJar.jar
I can delete the JAR's when the Classloader is closed.

No meta factory found for feature 'default'; this usually means that axiom-impl.jar is not in the classpath

From the answer in the link below Link
I found that it can be resolved by adding it to classpath. But I am using Custom ClassLoader to load jar axiom-impl-1.2.14.
Is there any way to achieve this?
axiom jar is using ClassLoader. Enumeration getResources(String name) to load that xmls internally in jar. XML file in our case is residing in jar file. So I am looking for solution by which I can get file URL of the XML.
Source Code :
public class ExternalClassLoader extends ClassLoader {
private String jarFile = "";
private Hashtable<String, Class> classes = new Hashtable<String, Class>();
public ExternalClassLoader(String jarLocation) {
super(ExternalClassLoader.class.getClassLoader());
this.jarFile = jarLocation;
}
#Override
public Class loadClass(String className) throws ClassNotFoundException {
return findClass(className);
}
#Override
public Class findClass(String className) {
byte classByte[];
Class result = null;
System.out.println("CLASS : " + className);
result = (Class) classes.get(className);
if (result != null) {
return result;
}
try {
return findSystemClass(className);
} catch (Exception e) {
}
JarFile jar = null;
try {
jar = new JarFile(jarFile);
String classLocation = className.replace('.', '/');
JarEntry entry = jar.getJarEntry(classLocation + ".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}
classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length, null);
classes.put(className, result);
return result;
} catch (Exception e) {
System.out.println("ERROR CLASS : " + className);
return null;
} finally {
try {
jar.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public InputStream getResourceAsStream(String name) {
try {
System.out.println("RESOURCE : " + jarFile + "//" + name);
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(name);
return jar.getInputStream(entry);
} catch (IOException e) {
System.out.println("ERROR RESOURCE : " + jarFile + "//" + name);
return null;
}
}
}
Since you don't specify the details, I'm assuming that the conflict occurs with another version of axiom-impl that is in the classpath of the class loader the rest of your application is loaded from (Otherwise you could just use one or more URLClassLoader instances or change the class loading policy of your application class loader).
I'm also assuming that (as you mentioned in a comment) axiom-api and axiom-impl are both loaded by the same custom class loader or that you combined the classes from these two JARs into a single JAR (in which case I'm assuming that you don't include axiom-dom in the same JAR since that would cause additional problems).
If these assumptions are true, then what you need is a class loader that loads classes from one or more JAR files and that uses parent last as class loading policy. To achieve that, you don't need to reimplement the JAR loading logic as you attempted to do in the code you have posted. Instead you can use URLClassLoader, but you need to extend it to change its class loading policy from the default parent first to parent last. There is actually an example of this in the source code of Apache Axiom itself:
http://svn.apache.org/repos/asf/webservices/axiom/tags/1.2.15/axiom-api/src/test/java/org/apache/axiom/util/stax/dialect/ParentLastURLClassLoader.java
You can probably use that code as is, although you may want to remove the package filter on javax.* because that shouldn't be necessary in your case.

Extracting .jar files to a directory, adding new files, then repacking into a .jar file in java

I was wondering whether there is a way to take a given .jar file, selected with a JFileChooser, extract it and put it into a new directory. Then, take all the files from another directory, add it to the directory with the extracted .jar file, and then take all that and package it back up again.
I'm doing this because I want a really easy way to install mods for that game, minecraft, where you can just select your minecraft.jar, and make sure the files for the mod are in a folder, and wait a bit, as indicated by a JProgressBar.
This is all I have so far
import java.io.*;
import java.util.jar.*;
import javax.swing.*;
public class Main extends JFrame {
public Main() {
super("Auto-mod installer");
setSize(300, 60);
setLocationRelativeTo(null);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JProgressBar bar = new JProgressBar(0, 100);
add(bar);
setVisible(true);
}
public static void main(String[] args) {
Main m = new Main();
}
private void extract(File f) {
//Hrm...
}
private void addModFiles() {
//Uh...
}
private void repackage(File f) {
//What?
}
}
As you can see, I have no idea what I'm doing. I do know what the imports needed are, but that's about it. Help would be appreciated, ranting about anything I did wrong would get me mad. Thanks!
EDIT: If you know a way to get the same results, and it's not the way that I was looking for, please let me know how to do so. As long as I get the results I was looking for, it would be great. Thanks again!
The idea is relatively simple. You have a few gotchas (like what to do if files already exist and that kind of thing), but otherwise...
I'd start by having a look at JarFile
(I'm in the middle of another example, but when I get time, I'll post some stuff)
UPDATE with Example
public class JarTest {
protected static final String OUTPUT_PATH = "..."; // The place you want to extact the jar to
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
new JarTest();
}
public JarTest() {
try {
unjar();
// Copy new contents in...
jar();
} catch (IOException exp) {
exp.printStackTrace();
}
}
// This just recursivly lists through all the files to be included in the new jar
// We don't care about the directories, as we will create them from the file
// references in the Jar ourselves
protected List<File> getFiles(File path) {
List<File> lstFiles = new ArrayList<File>(25);
// If you want the directories, add the "path" to the list now...
File[] files = path.listFiles();
if (files != null && files.length > 0) {
for (File file : files) {
if (file.isDirectory()) {
lstFiles.addAll(getFiles(file));
} else {
lstFiles.add(file);
}
}
}
return lstFiles;
}
// Re-Jar the contents
// You should always attempt to jar back to a new file, as you may not want to effect the original ;)
public void jar() throws IOException {
JarOutputStream jos = null;
try {
String outputPath = OUTPUT_PATH;
// Create a new JarOutputStream to the file you want to create
jos = new JarOutputStream(new FileOutputStream("...")); // Add your file reference
List<File> fileList = getFiles(new File(OUTPUT_PATH));
System.out.println("Jaring " + fileList.size() + " files");
// Okay, I cheat. I make a list of all the paths already added to the Jar only create
// them when I need to. You could use "file.isDirectory", but that would mean you would need
// to ensure that the files were sorted to allow all the directories to be first
// or make sure that the directory reference is added to the start of each recursion list
List<String> lstPaths = new ArrayList<String>(25);
for (File file : fileList) {
// Replace the Windows file seperator
// We only want the path to this element
String path = file.getParent().replace("\\", "/");
// Get the name of the file
String name = file.getName();
// Remove the output path from the start of the path
path = path.substring(outputPath.length());
// Remove the leading slash if it exists
if (path.startsWith("/")) {
path = path.substring(1);
}
// Add the path path reference to the Jar
// A JarEntry is considered to be a directory if it ends with "/"
if (path.length() > 0) {
// At the trailing path seperator
path += "/";
// Check to see if we've already added it out not
if (!lstPaths.contains(path)) {
// At the path entry...we need need this to make it easier to
// extract the files at a later state. There is a way to cheat,
// but I'll let you figure it out
JarEntry entry = new JarEntry(path);
jos.putNextEntry(entry);
jos.closeEntry();
// Make sure we don't try to add the same path entry again
lstPaths.add(path);
}
}
System.out.println("Adding " + path + name);
// Create the actual entry for this file
JarEntry entry = new JarEntry(path + name);
jos.putNextEntry(entry);
// Write the entry to the file
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
byte[] byteBuffer = new byte[1024];
int bytesRead = -1;
while ((bytesRead = fis.read(byteBuffer)) != -1) {
jos.write(byteBuffer, 0, bytesRead);
}
jos.flush();
} finally {
try {
fis.close();
} catch (Exception e) {
}
}
jos.closeEntry();
}
jos.flush();
} finally {
try {
jos.close();
} catch (Exception e) {
}
}
}
public void unjar() throws IOException {
JarFile jarFile = null;
try {
String outputPath = OUTPUT_PATH;
File outputPathFile = new File(outputPath);
// Make the output directories.
// I'll leave it up to you to decide how best to deal with existing content ;)
outputPathFile.mkdirs();
// Create a new JarFile reference
jarFile = new JarFile(new File("C:/hold/Java_Harmony.jar"));
// Get a list of all the entries
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
// Get the next entry
JarEntry entry = entries.nextElement();
// Make a file reference
File path = new File(outputPath + File.separator + entry.getName());
if (entry.isDirectory()) {
// Make the directory structure if we can
if (!path.exists() && !path.mkdirs()) {
throw new IOException("Failed to create output path " + path);
}
} else {
System.out.println("Extracting " + path);
// Extract the file from the Jar and write it to disk
InputStream is = null;
OutputStream os = null;
try {
is = jarFile.getInputStream(entry);
os = new FileOutputStream(path);
byte[] byteBuffer = new byte[1024];
int bytesRead = -1;
while ((bytesRead = is.read(byteBuffer)) != -1) {
os.write(byteBuffer, 0, bytesRead);
}
os.flush();
} finally {
try {
os.close();
} catch (Exception e) {
}
try {
is.close();
} catch (Exception e) {
}
}
}
}
} finally {
try {
jarFile.close();
} catch (Exception e) {
}
}
}
}
You can use this very simple library to pack/unpack jar file
JarManager
Very simple
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import fr.stevecohen.jarmanager.JarPacker;
import fr.stevecohen.jarmanager.JarUnpacker;
public class MyClass {
public void addFileToJar(String jarPath, String otherFilePath) {
try {
JarUnpacker jarUnpacker = new JarUnpacker();
File myJar = new File("./myfile.jar");
File otherFile = new File(otherFilePath);
Path unpackDir = Files.createTempDirectory(myJar.getName()); //create a temp directory to extract your jar
System.out.println("Unpacking in " + unpackDir.toString());
jarUnpacker.unpack(jarPath, unpackDir.toString()); //extraxt all files contained in the jar in temp directory
Files.copy(otherFile.toPath(), new File(unpackDir.toFile(), otherFile.getName()).toPath()); //copy your file
JarPacker jarRepacker = new JarPacker();
File newJar = new File("./maNewFile.jar");
System.out.println("Packing jar in " + newJar.getAbsolutePath());
jarRepacker.pack(unpackDir.toString(), newJar.getAbsolutePath()); //repack the jar with the new files inside
} catch (IOException e) {
e.printStackTrace();
}
}
}
You can also use maven dependency
<dependency>
<groupId>fr.stevecohen.jarmanager</groupId>
<artifactId>JarManager</artifactId>
<version>0.5.0</version>
</dependency>
You also need my repository
<repository>
<id>repo-reapersoon</id>
<name>ReaperSoon's repo</name>
<url>http://repo-maven.stevecohen.fr</url>
</repository>
Check the last version with the link bellow to use the last dependency
Please use my public issue tracker if you find some bugs

How a JAR file can read an external properties file

We have a connection pooling component (JAR file) for one of our application. As of now the application connection details are bundled with-in the JAR file (in .properties file).
Can we make it more generic? Can we have the client tell the properties file details (both the path and the file name) and use the JAR to get the connection?
Does it make sense to have something like this in the client code:
XyzConnection con = connectionIF.getConnection(uname, pwd);
Along with this, the client will specify (somehow???) the properties file details that has the URLs to connect, timeout etc.
Simplest way, use the -D switch to define a system property on a java command line.
That system property may contain a path to your properties file.
E.g
java -cp ... -Dmy.app.properties=/path/to/my.app.properties my.package.App
Then, in your code you can do ( exception handling is not shown for brevity ):
String propPath = System.getProperty( "my.app.properties" );
final Properties myProps;
if ( propPath != null )
{
final FileInputStream in = new FileInputStream( propPath );
try
{
myProps = Properties.load( in );
}
finally
{
in.close( );
}
}
else
{
// Do defaults initialization here or throw an exception telling
// that environment is not set
...
}
http://www.javaworld.com/javaworld/javaqa/2003-08/01-qa-0808-property.html
multiple approaches are available, the article above provides more details
ClassLoader.getResourceAsStream ("some/pkg/resource.properties");
Class.getResourceAsStream ("/some/pkg/resource.properties");
ResourceBundle.getBundle ("some.pkg.resource");
Just load the properties from file, something like
Properties properties = new Properties();
InputStreamReader in = null;
try {
in = new InputStreamReader(new FileInputStream("propertiesfilepathandname"), "UTF-8");
properties.load(in);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException ex) {}
}
}
Note how the encoding is explicitly specified as UTF-8 above. It could also be left out if you accept the default ISO8859-1 encoding, but beware with any special characters then.
This is my solution. First looking for app.properties in startup folder, if does not exists try to load from your JAR package:
File external = new File("app.properties");
if (external.exists())
properties.load(new FileInputStream(external));
else
properties.load(Main.class.getClassLoader().getResourceAsStream("app.properties"));
Simplest way is below. It will load application.properties from cfg folder outside of the jar file.
Directory Structure
|-cfg<Folder>-->application.properties
|-somerunnable.jar
Code:
Properties mainProperties = new Properties();
mainProperties.load(new FileInputStream("./cfg/application.properties"));
System.out.println(mainProperties.getProperty("error.message"));
In netbeans I needed to load application.properties from conf/ folder outside of the jar file.
Therefore I wrote :
public static String getProperty(String FileName, String Prop)
{
try {
FIS = new FileInputStream( "./../conf/"+FileName);
Properties properties;
(properties = new Properties()).load(FIS);
for(Enumeration propKeys = properties.propertyNames();
propKeys.hasMoreElements();){
String tmpKey = (String) propKeys.nextElement();
String tmpValue = properties.getProperty(tmpKey);
tmpValue = tmpValue.trim();
if (tmpKey.equals(Prop)){
//System.out.println(tmpKey +"="+tmpValue);
properties.put(tmpKey, tmpValue);
Value = properties.getProperty(Prop);
break;
}
}
if (Value==null){
throw new Exception("La Propiedad : "+Prop+" no Se encuentra en el Archivo de Configuracion");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return Value;
}
For Eclipse apply the following:
FIS = new FileInputStream( "../conf/"+FileName);
public static String getPropertiesValue(String propValue) {
Properties props = new Properties();
fileType = PCLLoaderLQIOrder.class.getClassLoader().getResourceAsStream(propFileName);
if (fileType != null) {
try {
props.load(fileType);
} catch (IOException e) {
logger.error(e);
}
} else {
try {
throw new FileNotFoundException("Property file" + propFileName + " not found in the class path");
} catch (FileNotFoundException e) {
logger.error(e);
}
}
String propertiesValue = props.getProperty(propValue);
return propertiesValue;
}
above methods works for me, just store your property file into directory from where to run your jar and provide that name in place of propFileName, when you want any value from property just call getPropertyValue("name").

Categories

Resources