I'm writing a short application that opens a .jar file, runs a few transforms on its contents, then wraps it up as a .jar file again. I'm using the approach from How to use JarOutputStream to create a JAR file? to create the new jar file and it's working with the exception that it's creating a file hierarchy in new jar file that stretches all the way back to my computer's root.
It's perfectly logical that this is happening since I'm passing the process the target directory as a path from the computer root. What I can't find is any other way of communicating the context that process needs to be able to find my target folder. How do I set the context from which I want the .jar file to be created?
To clarify:
I've rewritten the Run method of the solution linked above to accept two parameters: a string defining the name and location of the output jar file and a string defining the location of the folder I want compressed, like so:
public void run(String output, String inputDirectory) throws IOException
{
JarOutputStream target = new JarOutputStream(new FileOutputStream(output));
add(new File(inputDirectory), target, inputDirectory.length());
target.close();
}
Two sample values that I hand off to the method would be: C:/temp/964ca469-5f7b-4c56-8b5a-72b4c1c851e0/help.jar and C:/temp/964ca469-5f7b-4c56-8b5a-72b4c1c851e0/out/
I want the structure of my .jar file to have its root at the forward slash following "out", but instead the .jar file's hierarchy is:
C:
|-Temp
|-964ca469-5f7b-4c56-8b5a-72b4c1c851e0
|-out
|-{content}
I've tried passing the length of the string preceding the actual content to the Add method and paring it off before adding the JarEntry, but that just gets me an out of index error, which makes perfect sense because I'm just frikkin' groping.
There must be a way of setting the JarEntry class to a specific point in a folder hierarchy before adding a file, or some other means of doing the same thing, but I canna find it so far.
Thanks.
There is no 'root context' in a JAR file. What the files and paths are is completely dependent on what name(s) you put into the JarEntries.
Never figured out how to do it with Jar file creation, but as fge suggested the zip filesystem provider was indeed easier to work with. I used this in my main method:
CreateJarPackage zipper = new CreateJarPackage();
System.out.println(baseline + unique_directory + "/" + out_jar_file);
System.out.println(baseline + unique_directory + "/out/");
System.out.println(baseline.length() + unique_directory.length() + 5);
try {
zipper.addZipFiles(baseline + unique_directory + "/" + out_jar_file, baseline + unique_directory + "/out/", baseline.length() + unique_directory.length() + 5);
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
File folder = new File(baseline + unique_directory + "/out/");
File[] listOfFiles = folder.listFiles();
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isDirectory()) {
try {
zipper.addZipFiles(baseline + unique_directory + "/" + out_jar_file, listOfFiles[i].toString(), baseline.length() + unique_directory.length() + 5);
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
To call this:
import java.io.File;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
public class FinalZipCreator {
public static void processList(URI uri, Map<String, String> env, String path)
throws Throwable {
try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {
File folder = new File(path);
File[] listOfFiles = folder.listFiles();
int index = path.length();
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
Path externalTxtFile =Paths.get(listOfFiles[i].toString());
Path pathInZipfile = zipfs.getPath(listOfFiles[i]
.toString().substring(index));
// copy a file into the zip file
Files.copy(externalTxtFile, pathInZipfile,
StandardCopyOption.REPLACE_EXISTING);
} else if (listOfFiles[i].isDirectory()) {
Path externalTxtFile = Paths.get(listOfFiles[i].toString());
Path pathInZipfile = zipfs.getPath(listOfFiles[i]
.toString().substring(index));
// copy a file into the zip file
Files.copy(externalTxtFile, pathInZipfile,
StandardCopyOption.REPLACE_EXISTING);
File folder2 = new File(listOfFiles[i].toString());
File[] listOfFiles2 = folder2.listFiles();
int index2 = listOfFiles[i].toString().length();
for (int e = 0; e < listOfFiles2.length; e++) {
if (listOfFiles2[i].isFile()) {
Path externalTxtFile2 = Paths.get(listOfFiles2[e].toString());
Path pathInZipfile2 = zipfs.getPath(listOfFiles2[e]
.toString().substring(index2));
// copy a file into the zip file
Files.copy(externalTxtFile2, pathInZipfile2,
StandardCopyOption.REPLACE_EXISTING);
}
}
}
}}
}
}
There's likely a zillion better ways of doing it, but it worked. Thanks for the help.
As a side note, it's the equivalent of the "pathInZipFile" function that I could never locate in the Jar creator.
Related
I want to check if a directory is exist by using the notExists(Path path, LinkOption... options) and Im confused with the LinkOption.NOFOLLOW_LINKS although after I googled I still not quite get when to use it. Here are my codes:
import java.io.*;
import java.nio.file.Files;
import java.nio.file.*;
import java.util.ArrayList;
public class Test
{
public static void main(String[] args) throws IOException
{
Path source = Paths.get("Path/Source");
Path destination = Paths.get("Path/Destination");
ArrayList<String> files = new ArrayList<String>();
int retry = 3;
// get files inside source directory
files = getFiles(source);
// move all files inside source directory
for (int j=0; j < files.size(); j++){
moveFile(source,destination,files.get(j),retry);
}
}
// move file to destination directory
public static void moveFile(Path source, Path destination, String file, int retry){
while (retry>0){
try {
// if destination path not exist, create directory
if (Files.notExists(destination, LinkOption.NOFOLLOW_LINKS)) {
// make directory
Files.createDirectories(destination);
}
// move file to destination path
Path temp = Files.move(source.resolve(file),destination.resolve(file));
// if successfully, break
if(temp != null){
break;
}
// else, retry
else {
--retry;
}
} catch (Exception e){
// retry if error occurs
--retry;
}
}
}
// get all file names in source directory
public static ArrayList<String> getFiles(Path source){
ArrayList<String> filenames = new ArrayList<String>();
File folder = new File(source.toString());
File[] listOfFiles = folder.listFiles(); // get all files inside the source directory
for (int i = 0; i < listOfFiles.length; i++) {
if (listOfFiles[i].isFile()) {
filenames.add(listOfFiles[i].getName()); // add file's name into arraylist
}
}
return filenames;
}
}
The result of using LinkOption.NOFOLLOW_LINKS and not using it are the same (The files are transferred to the destination). So, Im guessing for my case, i can ignore the Link option? also, in what situation will i be needing that? Thanks!
So, Im guessing for my case, i can ignore the Link option?
You can only follow a link if the link exists.
So if you are testing to make sure a directory doesn't exist, there are two outcomes.
it exists, so there is no need to follow the link.
it doesn't exist, so there is nothing to follow.
in what situation will i be needing that?
Did you look at my answer in the link I provided you? I tried to give a simple example.
I have a program that is supposed to rename all files within an entire folder (with sub-folders) to a temporary file name, copy those to a different directory, then change the temp filename back to the original filename. During this process I would like to keep all folder names the same. When I run the code below, all it does is change the name of the top-level folders in the path that i specify:
package shortenFilenames;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class shortenFilenameClass
{
public static void main(String[] args) throws IOException
{
String absolutePathLocal = "C:\\Users\\talain\\Desktop\\marketingOriginal"; //original files
String absolutePathOnedrive= "C:\\Users\\talain\\Desktop\\fakeOnedrive"; //path to onedrive
File local = new File(absolutePathLocal);
File onedrive = new File(absolutePathOnedrive);
File[] filesInDir = local.listFiles();
for(int i = 0; i < filesInDir.length; i++)
{
String name = filesInDir[i].getName();
System.out.println(name);
String newName = String.valueOf(i);
File oldPath = new File(absolutePathLocal + "\\" + newName);
System.out.println("oldPath: " + oldPath);
filesInDir[i].renameTo(new File(oldPath.toString()));
File newPath = new File(absolutePathOnedrive + "\\" + newName);
copyFileUsingJava7Files(oldPath, newPath);
newPath.renameTo(new File(newPath.toString()));
System.out.println("renamed: " + name + "to: " + newName + ", copied to one drive, and changed back to original name");
}
}
private static void copyFileUsingJava7Files(File source, File dest) throws IOException {
Files.copy(source.toPath(), dest.toPath());
}
}
I just did the following and it copied whatever files were in the first path into the second path. If it fails for you, show me the error output.
for(int i = 0; i < filesInDir.length; i++)
{
String name = filesInDir[i].getName();
System.out.println(name);
Files.copy(new File(absolutePathLocal + "\\" + name).toPath(),new File(absolutePathOnedrive + "\\" + name).toPath());
}
Your code also assumes that those directories already exist on your system otherwise you will run into a FileNotFound exception.
I want to decompress a large folder in ZIP format with nested subdirectories in a directory that already exists. The files inside the ZIP folder can exist in the decompressed directory. I need to keep the previous files only when the date of that file is newer than the date of the file in the ZIP folder. If the file in the ZIP is newer, then I want to overwrite it.
There is some good strategy for doing this? I already checked truezip and zip4j, but I can't find the option (the best option for me so far is modifying the zip4j sources, but it should be a better way.
P.S. If I haven't explained this correctly, please feel free to ask. English is not my native language and I could have expressed anything wrong..
Thanks.
With Zip4j, this is how it can be done:
import java.io.File;
import java.util.Date;
import java.util.List;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.util.Zip4jUtil;
public class ExtractWithoutOverwriting {
public static void main(String[] args) {
try {
String outputPath = "yourOutputPath";
ZipFile zipFile = new ZipFile(new File("yourZipFile.zip"));
if (zipFile.isEncrypted()) {
zipFile.setPassword("yourPassword".toCharArray());
}
#SuppressWarnings("unchecked")
List<FileHeader> fileHeaders = zipFile.getFileHeaders();
for (FileHeader fileHeader : fileHeaders) {
if (fileHeader.isDirectory()) {
File file = new File(outputPath + System.getProperty("file.separator") + fileHeader.getFileName());
file.mkdirs();
} else {
if (canWrite(outputPath, fileHeader)) {
System.out.println("Writing file: " + fileHeader.getFileName());
zipFile.extractFile(fileHeader, outputPath);
} else {
System.out.println("Not writing file: " + fileHeader.getFileName());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static boolean canWrite(String outputPath, FileHeader fileHeader) {
File file = new File(outputPath + System.getProperty("file.separator") + fileHeader.getFileName());
//time stamps are stored in dos format in a zip file
//convert it to java format
long lastModifiedFromZip = Zip4jUtil.dosToJavaTme(fileHeader.getLastModFileTime());
//If the file exists, it can be overwritten only if the file in the destination path
//is newer than the one in the zip file
return !(file.exists() && isLastModifiedDateFromFileNewer(file.lastModified(), lastModifiedFromZip));
}
public static boolean isLastModifiedDateFromFileNewer(long lastModifiedFromFile, long lastModifiedFromZip) {
Date lastModifiedDateFromFile = new Date(lastModifiedFromFile);
Date lastModifiedDateFromZip = new Date(lastModifiedFromZip);
return lastModifiedDateFromFile.after(lastModifiedDateFromZip);
}
}
What we do here is:
Create a new instance of the ZipFile
If the zip file is encrypted, set a password
Loop over all files in the zip file
Check if a file with this name exists in the output path and if this file's last modification time is "newer" than the one in the zip file. This check is done in the method: canWrite()
This code is not completely tested, but I hope it gives you an idea of a solution.
I'm trying to get a backup program to take a folder selected by the user using JFileChooser and copy it to a destination also selected using the same method.
The only problem is that it doesn't put all the contents of the selected folder into a folder in the destination named the same thing, and I really dont know why. I did some Googling, and I didnt find anything useful.
Here's the code:
package main;
import java.io.File;
import java.util.ArrayList;
public class test {
BackgroundWorker bw;
static ArrayList bgWorker = new ArrayList();
ArrayList al = new ArrayList(); // this is the list of files selected to
// back up
String dir = ""; // this is the path to back everything up to selected by
static // the user
boolean bwInitiallized = false;
public void startBackup() throws Exception {
Panel.txtArea.append("Starting Backup...\n");
for (int i = 0; i < al.size(); i++) {
/**
* THIS IS WHERE I NEED TO CREATE THE FOLDER THAT EACH BACKUP FILE
* WILL GO INTO EX: SC2 GOES INTO A FOLDER CALLED SC2 AND RIOT GOES
* TO RIOT, ALL WITHIN THE DIRECTORY CHOSEN
*/
File file = new File((String) al.get(i));
File directory = new File(dir);
// File dirFile = new File(dir + "\\" + file.getName());
// if (!dirFile.exists())
// dirFile.mkdir();
bw = new BackgroundWorker(Panel.txtArea, file, directory);
bgWorker.add(bw);
bwInitiallized = true;
bw.execute();
/**
* follows to the bottom of the txtarea
*/
int x;
Panel.txtArea.selectAll();
x = Panel.txtArea.getSelectionEnd();
Panel.txtArea.select(1, x);
}
clearList(); // method not included in this example that deletes all the
// contents of the al array list.
}
public static void cancel() {
BackgroundWorker bg;
if (bwInitiallized) {
bwInitiallized = false;
Panel.txtArea.append("Cancelling...\n");
for (int i = 0; i < bgWorker.size(); i++) {
// BackgroundWorker bg = (BackgroundWorker) bgWorker.get(i);
bg = (BackgroundWorker) bgWorker.get(i);
bg.cancel(true);
}
Panel.txtArea.append("Canceled backUp!\n");
} else {
Panel.txtArea.append("Cannot Cancel! Not Initiallized!\n");
}
}
}
What I think the problem is: I believe that for whatever reason the destination file path needs to have to the name of the folder included, but I tried that and it didnt help.
Does anyone know what I'm doing wrong?
This is the code that makes the JFileChooser:
public void fileChooserToDestination() {
LookAndFeel previousLF = UIManager.getLookAndFeel();
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
JFileChooser jfc = new JFileChooser();
try {
UIManager.setLookAndFeel(previousLF);
} catch (UnsupportedLookAndFeelException e) {
}
jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (jfc.showDialog(null, "Select Directory") == JFileChooser.APPROVE_OPTION) {
File file = jfc.getSelectedFile();
dir = file.getPath();
Panel.txtArea.append("User selected " + file.getPath()
+ " for the destination...\n");
try {
startBackup();
} catch (Exception e) {
}
} else {
Dialogs.msg("You canceled selecting a destination folder! Returning to main screen...");
al.clear();
Panel.txtArea.append("User cancelled the destination selection..."
+ "\n");
}
return;
}
Parts of the code I need are missing. I can't see where you're making your decisions about how to append the source file to the destination path, so I wrote this quick example it illustrate the point...
File sourcePath = new File("/path/to/be/backed/up");
File destPath = new File("X:/BackupHere");
// Get all the files from sourcePath
List<File> listFiles = getFilesFrom(sourcePath);
for (File toBackup : listFiles) {
// Now we need to strip off the sourcePath
// Get the name of the file
String fileName = toBackup.getName();
// Get parent folder's path
String path = toBackup.getParent();
// Remove the source path from file path
path = path.substring(sourcePath.getPath().length());
// Append the file name to the path
path = path + File.separator + fileName;
// Now we have the name of the back up file
String backupFile = destPath + path;
System.out.println("Backup to " + backupFile);
}
Basically, you need to strip of the "source path" (the directory you want to copy). You then use the resulting value to append to the "backup path" value and you should then have a suitable path.
How can I reflectively get all of the packages in the project? I started with Package.getPackages(), but that only got all the packages associated to the current package. Is there a way to do this?
#PhilippWendler's comment led me to a method of accomplishing what I needed. I tweaked the method a little to make it recursive.
/**
* Recursively fetches a list of all the classes in a given
* directory (and sub-directories) that have the #UnitTestable
* annotation.
* #param packageName The top level package to search.
* #param loader The class loader to use. May be null; we'll
* just grab the current threads.
* #return The list of all #UnitTestable classes.
*/
public List<Class<?>> getTestableClasses(String packageName, ClassLoader loader) {
// State what package we are exploring
System.out.println("Exploring package: " + packageName);
// Create the list that will hold the testable classes
List<Class<?>> ret = new ArrayList<Class<?>>();
// Create the list of immediately accessible directories
List<File> directories = new ArrayList<File>();
// If we don't have a class loader, get one.
if (loader == null)
loader = Thread.currentThread().getContextClassLoader();
// Convert the package path to file path
String path = packageName.replace('.', '/');
// Try to get all of nested directories.
try {
// Get all of the resources for the given path
Enumeration<URL> res = loader.getResources(path);
// While we have directories to look at, recursively
// get all their classes.
while (res.hasMoreElements()) {
// Get the file path the the directory
String dirPath = URLDecoder.decode(res.nextElement()
.getPath(), "UTF-8");
// Make a file handler for easy managing
File dir = new File(dirPath);
// Check every file in the directory, if it's a
// directory, recursively add its viable files
for (File file : dir.listFiles()) {
if (file.isDirectory())
ret.addAll(getTestableClasses(packageName + '.' + file.getName(), loader));
}
}
} catch (IOException e) {
// We failed to get any nested directories. State
// so and continue; this directory may still have
// some UnitTestable classes.
System.out.println("Failed to load resources for [" + packageName + ']');
}
// We need access to our directory, so we can pull
// all the classes.
URL tmp = loader.getResource(path);
System.out.println(tmp);
if (tmp == null)
return ret;
File currDir = new File(tmp.getPath());
// Now we iterate through all of the classes we find
for (String classFile : currDir.list()) {
// Ensure that we only find class files; can't load gif's!
if (classFile.endsWith(".class")) {
// Attempt to load the class or state the issue
try {
// Try loading the class
Class<?> add = Class.forName(packageName + '.' +
classFile.substring(0, classFile.length() - 6));
// If the class has the correct annotation, add it
if (add.isAnnotationPresent(UnitTestable.class))
ret.add(add);
else
System.out.println(add.getName() + " is not a UnitTestable class");
} catch (NoClassDefFoundError e) {
// The class loader could not load the class
System.out.println("We have found class [" + classFile + "], and couldn't load it.");
} catch (ClassNotFoundException e) {
// We couldn't even find the damn class
System.out.println("We could not find class [" + classFile + ']');
}
}
}
return ret;
}
It's possible but tricky and expensive, since you need to walk the classpath yourself. Here is how TestNG does it, you can probably extract the important part for yourself:
https://github.com/cbeust/testng/blob/master/src/main/java/org/testng/internal/PackageUtils.java
This approach prints all packages only (at least a root "packageName" has to be given first).
It is derived from above.
package devTools;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class DevToolUtil {
/**
* Method prints all packages (at least a root "packageName" has to be given first).
*
* #see http://stackoverflow.com/questions/9316726/reflectively-get-all-packages-in-a-project
* #since 2016-12-05
* #param packageName
* #param loader
* #return List of classes.
*/
public List<Class<?>> getTestableClasses(final String packageName, ClassLoader loader) {
System.out.println("Exploring package: " + packageName);
final List<Class<?>> ret = new ArrayList<Class<?>>();
if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
}
final String path = packageName.replace('.', '/');
try {
final Enumeration<URL> res = loader.getResources(path);
while (res.hasMoreElements()) {
final String dirPath = URLDecoder.decode(res.nextElement().getPath(), "UTF-8");
final File dir = new File(dirPath);
if (dir.listFiles() != null) {
for (final File file : dir.listFiles()) {
if (file.isDirectory()) {
final String packageNameAndFile = packageName + '.' + file.getName();
ret.addAll(getTestableClasses(packageNameAndFile, loader));
}
}
}
}
} catch (final IOException e) {
System.out.println("Failed to load resources for [" + packageName + ']');
}
return ret;
}
public static void main(final String[] args) {
new DevToolUtil().getTestableClasses("at", null);
}
}
May be off-topic (because it is not exactly in terms of java "reflection") ...
However, how about the following solution:
Java packages can be treated as folders (or directories on Linux\UNIX).
Assuming that you have a root package and its absolute path is known, you can recursively print all sub-folders that have *.java or *.class files using the following batch as the basis:
#echo off
rem List all the subfolders under current dir
FOR /R "." %%G in (.) DO (
Pushd %%G
Echo now in %%G
Popd )
Echo "back home"
You can wrap this batch in java or if you run on Linux\Unix rewrite it in some shell script.
Good Luck!
Aviad.