How to populate JavaFX TreeView using JarEntries (packages - files) - java

Baically, I'm looping through JarEntry classes from a JarFile and trying to add each package as a node on the TreeView<String>.
Annoyingly, the packages are returned being split up by slashes. Meaning: I have to split each package name into an array and then check if each part (package) already exists in the tree.
Here's an example of what I'm working with:
org/something/commons
org/something/commons/more
I need to somehow work with each of these strings to create this kind of structure:
root
org
something
commons
more
After that I'll need to add each class file from non directory entries to each pre-existing directory nodes.
This is honestly the more confusing thing I've ever tried to achieve. I just can't think up a good algorithm for doing this except creating some form of extending treeitem class that acts as an entry wrapper or something like that.
Any guidance would be much appreciated. My current code is like:
private void populateTree(Enumeration<String> jarEntries) {
jarFile.stream().forEach(entry -> {
String entryName = entry.getName();
if (entry.isDirectory()) {
String[] packages = entryName.split("/");
for(String packageName : packages) {
// check if already exists in root node
if(root.getChildren().contains(root.getChildren().indexOf(packageName))) {
TreeItem<String> packageNode = root.getChildren().get(root.getChildren().indexOf(packageName));
packageNode.getChildren().add(new TreeItem<String>(packageName));
} else {
root.getChildren().add(new TreeItem<String>(packageName));
}
}
} else {
// it's a file
String fileName = entryName.substring(entryName.lastIndexOf("/"), entryName.length());
String[] packages = entryName.substring(0, entryName.lastIndexOf("/")).split("/");
// somehow loop through each child of the root node and eventually, using some form of traversal algo, get to the package node to add new item to
}
});
root.setExpanded(true);
}
This produces the incorrect output of:

I would create a TreeView<JarEntry>, so the data wrapped by the TreeItems are the JarEntry objects. Then use a cellFactory to instruct the cells to display only the last part of the path.
Actually populating the tree is kind of tricky, because a jar file doesn't need to have entries for its directories. So you may end up having to create new entries as you build the structure. I'm not sure I follow the method you posted: aren't you adding all packages and their subpackages directly to the root (instead of subpackages to packages)?
Here's a SSCCE. You might be able to find a cleaner implementation of populating the tree...
import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Stage;
public class JarFileTreeView extends Application {
#Override
public void start(Stage primaryStage) {
TreeView<JarEntry> tree = new TreeView<>();
tree.setShowRoot(false);
TreeItem<JarEntry> root = new TreeItem<>();
tree.setRoot(root);
// only display last portion of the path in the cells:
tree.setCellFactory(tv -> new TreeCell<JarEntry>() {
#Override
public void updateItem(JarEntry item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
} else {
String[] pathElements = item.getName().split("/");
setText(pathElements[pathElements.length - 1]);
}
}
});
ObjectProperty<JarFile> jarFile = new SimpleObjectProperty<>();
jarFile.addListener((obs, oldFile, newFile) -> {
if (newFile == null) {
root.getChildren().clear();
} else {
populateTree(root, newFile);
}
});
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new ExtensionFilter("Jar Files", "*.jar"));
Button loadButton = new Button("Load...");
loadButton.setOnAction(e -> {
File file = chooser.showOpenDialog(primaryStage);
if (file != null) {
try {
jarFile.set(new JarFile(file));
} catch (Exception e1) {
e1.printStackTrace();
}
}
});
BorderPane uiRoot = new BorderPane(tree, null, null, loadButton, null);
BorderPane.setMargin(loadButton, new Insets(10));
BorderPane.setAlignment(loadButton, Pos.CENTER);
Scene scene = new Scene(uiRoot, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void populateTree(TreeItem<JarEntry> root, JarFile file) {
root.getChildren().clear();
List<JarEntry> entries = file.stream().collect(Collectors.toList());
// sort by length of path (i.e. number of components separated by "/"), then by name:
entries.sort(Comparator
.comparing((JarEntry entry) -> entry.getName().split("/").length)
.thenComparing(entry -> {
String[] pathElements = entry.getName().split("/");
return pathElements[pathElements.length - 1];
}));
for (JarEntry entry : entries) {
// need to find correct parent for entry. That parent (or any of the ancestors)
// might not exist yet, so we create it if necessary as we search.
// Split name up into folder, subfolder, etc:
List<String> pathElements = Arrays.asList(entry.getName().split("/"));
// Search for parent. Start at root:
TreeItem<JarEntry> parent = root;
// Iterate through all elements except the last, traversing tree:
for (int i = 0; i < pathElements.size() - 1 ; i++) {
// name of ancestor entry:
String matchingName = String.join("/", pathElements.subList(0, i+1));
final TreeItem<JarEntry> current = parent ;
// update parent with current parent's descendant, matching appropriate name:
parent = current.getChildren().stream()
.filter(child -> child.getValue().getName().equals(matchingName))
.findFirst()
// it's possible this ancestor didn't yet exist, so we create it,
// and add it to the correct parent:
.orElseGet(() -> {
JarEntry newEntry = new JarEntry(matchingName);
TreeItem<JarEntry> newItem = new TreeItem<>(newEntry);
current.getChildren().add(newItem);
return newItem ;
});
}
// after all that, we have a valid parent:
parent.getChildren().add(new TreeItem<>(entry));
}
}
public static void main(String[] args) {
launch(args);
}
}

Related

Spring Boot REST API return list of files

I am trying to return a list of files from a directory. Here's my code:
package com.demo.web.api.file;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.demo.core.Logger;
import io.swagger.v3.oas.annotations.Operation;
#RestController
#RequestMapping(value = "/files")
public class FileService {
private static final Logger logger = Logger.factory(FileService.class);
#Value("${file-upload-path}")
public String DIRECTORY;
#Value("${file-upload-check-subfolders}")
public boolean CHECK_SUBFOLDERS;
#GetMapping(value = "/list")
#Operation(summary = "Get list of Uploaded files")
public ResponseEntity<List<File>> list() {
List<File> files = new ArrayList<>();
if (CHECK_SUBFOLDERS) {
// Recursive check
try (Stream<Path> walk = Files.walk(Paths.get(DIRECTORY))) {
List<Path> result = walk.filter(Files::isRegularFile).collect(Collectors.toList());
for (Path p : result) {
files.add(p.toFile().getAbsoluteFile());
}
} catch (Exception e) {
logger.error(e.getMessage());
}
} else {
// Checks the root directory only.
try (Stream<Path> walk = Files.walk(Paths.get(DIRECTORY), 1)) {
List<Path> result = walk.filter(Files::isRegularFile).collect(Collectors.toList());
for (Path p : result) {
files.add(p.toFile().getAbsoluteFile());
}
} catch (Exception e) {
logger.error(e.getMessage());
}
}
return ResponseEntity.ok().body(files);
}
}
As seen in the code, I am trying to return a list of files.
However, when I test in PostMan, I get a list of string instead.
How can I make it return the file object instead of the file path string? I need to get the file attributes (size, date, etc.) to display in my view.
I would recommend that you change your ResponseEntity<> to return not a List of File but instead, a List of Resource, which you can then use to obtain the file metadata that you need.
public ResponseEntity<List<Resource>> list() {}
You can also try specifying a produces=MediaType... param in your #GetMapping annotation so as to tell the HTTP marshaller which kind of content to expect.
You'd have to Create a separate payload with the details you wanna respond with.
public class FilePayload{
private String id;
private String name;
private String size;
public static fromFile(File file){
// create FilePayload from File object here
}
}
And convert it using a mapper from your internal DTO objects to payload ones.
final List<FilePayload> payload = files.forEach(FilePayload::fromFile).collect(Collectors.toList());
return new ResponseEntity<>(payload, HttpStatus.OK);
I think you should not return a body in this case as you may be unaware of the size.
Better to have another endpoint to GET /files/{id}
I did give this another thought. What I just needed was the filename, size and date of the file. From there, I can get the file extension and make my list display look good already.
Here's the refactored method:
#GetMapping(value = "/list")
#Operation(summary = "Get list of Uploaded files")
public ResponseEntity<String> list() {
JSONObject responseObj = new JSONObject();
List<JSONObject> files = new ArrayList<>();
// If CHECK_SUBFOLDERS is true, pass MAX_VALUE to make it recursive on all
// sub-folders. Otherwise, pass 1 to use the root directory only.
try (Stream<Path> walk = Files.walk(Paths.get(DIRECTORY), CHECK_SUBFOLDERS ? MAX_VALUE : 1)) {
List<Path> result = walk.filter(Files::isRegularFile).collect(Collectors.toList());
for (Path p : result) {
JSONObject file = new JSONObject();
file.put("name", p.toFile().getName());
file.put("size", p.toFile().length());
file.put("lastModified", p.toFile().lastModified());
files.add(file);
}
responseObj.put("data", files);
} catch (Exception e) {
String errMsg = CoreUtils.formatString("%s: Error reading files from the directory: \"%s\"",
e.getClass().getName(), DIRECTORY);
logger.error(e, errMsg);
responseObj.put("errors", errMsg);
}
return ResponseEntity.ok().body(responseObj.toString());
}
The above was what I ended up doing. I created a JSONObject of the props I need, and then returned the error if it did not succeed. This made it a lot better for me.

Java - traverse through folders and do something if intended folder is present

Being very new to Java, I'm unable to bring a small concept to a syntactic form. Apologies.
My project structure looks like below & i'm trying to walk thru the sub folders of applications
directory & search for a folder named conduit, if present, create a new folder called base parallel to it.
At best I came up with the below code, post that, kind of struggling.
/home/project_A/applications
|sub_project_A
|target
|conduit
|sub_project_B
|target
|conduit
|sub_project_C
|target
|class
|sub_project_D
|target
|conduit
public class Test
{
public static void main(String[] args)
{
Path currentRelativePath = Paths.get("");
String s = currentRelativePath.toAbsolutePath().toString();
String appDir = s + "/applications";
System.out.println("Directory Exists" + appDir);
}
}
You can use BFS to traverse the sub directories:
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.Queue;
public class MyClass {
public static void main(String[] args) {
Path currentRelativePath = Paths.get("");
Queue<Path> paths = new LinkedList<>();
paths.add(currentRelativePath);
while(!paths.isEmpty()) {
Path current = paths.poll();
File currentFile = current.toFile();
if(currentFile.isDirectory()) {
if(currentFile.getName().equals("conduit")) {
// Found the directory called conduit, Do what you have to do here
}else {
for(String fileName : currentFile.list()) {
paths.add(Paths.get(currentFile.getAbsolutePath()+"/"+fileName));
}
}
}
}
}
}
Using Files.find is a good way to traverse a series of directories quickly to find files or directories matching any criteria you need. Here is an example which prints off the matches:
BiPredicate<Path, BasicFileAttributes> predicate = (p,a) -> a.isDirectory() && "conduit".equals(p.getFileName().toString());
try(var dirs = Files.find(Path.of("."), Integer.MAX_VALUE, predicate)) {
dirs.forEach(p -> {
System.out.println("Found "+p+", create if not exists: "+p.resolveSibling("base"));
}
);
}

How to replace a PDAnnotationLink's default action with PDActionJavaScript one?

I need to find all PDAnnotationLink in a document and replace their default actions with PDActionJavaScript ones.
The final goal is to make the link open in a new tab but for now I just want alert to be displayed on click.
Here is the test example:
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLink;
import org.junit.Test;
/**
*
*/
public class PdfBoxTest {
#Test
public void testReplaceLinks() throws Exception {
String pathPrefix = ...path to pdf file with links...;
Path originalPdf = Paths.get(pathPrefix + "links_test.pdf");
PDDocument doc = PDDocument.load(originalPdf.toFile());
doc.getDocumentCatalog().getPages().forEach(page -> {
try {
page.getAnnotations().forEach(pdAnnotation -> {
if (pdAnnotation instanceof PDAnnotationLink) {
PDAnnotationLink link = (PDAnnotationLink) pdAnnotation;
PDAction action = link.getAction();
if (action instanceof PDActionURI) {
PDActionURI uri = (PDActionURI) action;
PDActionJavaScript javaScript =
new PDActionJavaScript("app.alert(\"" + uri.getURI() + "\");");
// actual goal of replacing the links is to open them in a new tab/window
// PDActionJavaScript javaScript =
// new PDActionJavaScript("app.launchURL(\"" + uri.getURI() + "\", true);");
link.setAction(javaScript);
}
}
});
} catch (Throwable t) {
throw new RuntimeException(t);
}
});
Path modifiedPdf = Paths.get(pathPrefix + "links_test_mod.pdf");
doc.save(modifiedPdf.toFile());
}
}
but the document just renders the link label which is not clickable (no alert is shown).
I'm sure I'm missing something here.
Here are the links to the PDF files.
Original:
https://www.dropbox.com/s/notwf6yde5okh7g/links_test_orig.pdf?dl=0
Modified:
https://www.dropbox.com/s/bejj6rawwjrbyyc/links_test_mod.pdf?dl=0

Subdirectory classloading fails with modular JavaFX

I made a Gradle project which uses a classloader to load a text file from the subdirectory resources/text. At this point it works, but when I convert the project into a modular JavaFX program, the same classloader function gives a NullPointerException.
src/main project directory
└───src
└───main
├───java
│ │ module-info.java
│ └───app
│ Main.java
└───resources
└───text
words.txt
build.gradle
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
sourceCompatibility = 11
repositories {
jcenter()
}
javafx {
version = '13.0.1'
modules = [ 'javafx.controls']
}
mainClassName = 'exMod/app.Main'
jar {
manifest {
attributes 'Main-Class': mainClassName
}
}
module-info.java
module exMod {
requires javafx.controls;
exports app;
}
Main.java
package app;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Main extends Application {
public static void main(String[] args){ launch(); }
public void start(Stage stage){
stage.setTitle("Hello World!");
Button button = new Button();
Label label = new Label();
button.setText("Click me!");
button.setOnAction(event -> label.setText(getWords()));
VBox root = new VBox();
root.setAlignment(Pos.CENTER);
root.getChildren().addAll(button, label);
stage.setScene(new Scene(root, 300, 250));
stage.show();
}
private String getWords(){
String path = "text/words.txt";
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);
InputStreamReader isReader = new InputStreamReader(inputStream); // null exception
BufferedReader reader = new BufferedReader(isReader);
StringBuilder content = new StringBuilder();
try {
String line;
while( (line = reader.readLine()) != null) {
content.append(line);
}
} catch(IOException e){
e.printStackTrace();
}
return content.toString();
}
}
If I change the path variable to "words.txt" and move the text file up one level to src/main/resources, it works, but for a reason I haven't been able to discover, loading the file from a resource subdirectory will not work. What is causing the NullPointerException and how can I fix it?
In a comment to this question, as an alternative to program.class.getClassLoader().getResource("b/text.txt") the user Holger states
... you should use program.class.getResource("b/text.txt"), to
resolve a resource relative to the location of the program class.
Otherwise, it may fail in Java 9 or newer, once you use modules, as
going up to the class loader will destroy the information about the
actual module.
Because of this I tried to use just getClass().getResourceAsStream(path) instead of getClass().getClassLoader().getResourceAsStream(path).
I also read Alan Bateman's comment on this question where he states
The resource is encapsulated so it can't be found with the ClassLoader
APIs. I assume
ComboBoxStyling.class..getResource("/css/styleclass.css") will work -
note the leading slash so that it is found relative to the root
location of the module, not relative to ComboBoxStyling.class in this
case.
So I happened to try this combination together, and it worked!
getWords()
private String getWords(){
String path = "/text/words.txt";
InputStream in = getClass().getResourceAsStream(path);
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder content = new StringBuilder();
try {
String line;
while( (line = reader.readLine()) != null) {
content.append(line);
}
} catch(IOException e){
e.printStackTrace();
}
return content.toString();
}

Moving large files in java

I have to move files from one directory to other directory.
Am using property file. So the source and destination path is stored in property file.
Am haivng property reader class also.
In my source directory am having lots of files. One file should move to other directory if its complete the operation.
File size is more than 500MB.
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import static java.nio.file.StandardCopyOption.*;
public class Main1
{
public static String primarydir="";
public static String secondarydir="";
public static void main(String[] argv)
throws Exception
{
primarydir=PropertyReader.getProperty("primarydir");
System.out.println(primarydir);
secondarydir=PropertyReader.getProperty("secondarydir");
File dir = new File(primarydir);
secondarydir=PropertyReader.getProperty("secondarydir");
String[] children = dir.list();
if (children == null)
{
System.out.println("does not exist or is not a directory");
}
else
{
for (int i = 0; i < children.length; i++)
{
String filename = children[i];
System.out.println(filename);
try
{
File oldFile = new File(primarydir,children[i]);
System.out.println( "Before Moving"+oldFile.getName());
if (oldFile.renameTo(new File(secondarydir+oldFile.getName())))
{
System.out.println("The file was moved successfully to the new folder");
}
else
{
System.out.println("The File was not moved.");
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
System.out.println("ok");
}
}
}
My code is not moving the file into the correct path.
This is my property file
primarydir=C:/Desktop/A
secondarydir=D:/B
enter code here
Files should be in B drive. How to do? Any one can help me..!!
Change this:
oldFile.renameTo(new File(secondarydir+oldFile.getName()))
To this:
oldFile.renameTo(new File(secondarydir, oldFile.getName()))
It's best not to use string concatenation to join path segments, as the proper way to do it may be platform-dependent.
Edit: If you can use JDK 1.7 APIs, you can use Files.move() instead of File.renameTo()
Code - a java method:
/**
* copy by transfer, use this for cross partition copy,
* #param sFile source file,
* #param tFile target file,
* #throws IOException
*/
public static void copyByTransfer(File sFile, File tFile) throws IOException {
FileInputStream fInput = new FileInputStream(sFile);
FileOutputStream fOutput = new FileOutputStream(tFile);
FileChannel fReadChannel = fInput.getChannel();
FileChannel fWriteChannel = fOutput.getChannel();
fReadChannel.transferTo(0, fReadChannel.size(), fWriteChannel);
fReadChannel.close();
fWriteChannel.close();
fInput.close();
fOutput.close();
}
The method use nio, it make use os underling operation to improve performance.
Here is the import code:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
If you are in eclipse, just use ctrl + shift + o.

Categories