Subdirectory classloading fails with modular JavaFX - java

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();
}

Related

eclipse plugin/application writing into an cvs file

so I am making a software for the eclipse, that tracks the changes made by the user, I have all the data available but now I need to put it into a cvs file located somewhere in the project folder, as the program should be able to be used by anyone without changing the file path.
ps the normal file path works, so I know that the code is alright, just don't seem to be able to print in the project folder.
try {
PrintWriter pw = new PrintWriter(new File("logger.csv"));
StringBuilder sb = new StringBuilder();
sb.append("time");
sb.append(",");
sb.append("action");
sb.append(",");
sb.append("info");
sb.append("\n");
sb.append("17:00");
sb.append(",");
sb.append("create");
sb.append(",");
sb.append("test");
sb.append("\n");
sb.append("17:40");
sb.append(",");
sb.append("del");
sb.append(",");
sb.append("test2");
sb.append("\n");
pw.write(sb.toString());
pw.close();
From an Eclipse plug-in you should use Eclipse's resource API to make sure resource changes are synchronized with the workspace:
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.ICoreRunnable;
import org.eclipse.core.runtime.NullProgressMonitor;
public class Foo {
public void bar() throws CoreException {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
workspace.run((ICoreRunnable) monitor -> {
IWorkspaceRoot root = workspace.getRoot();
IProject project = root.getProject("projectName");
IFile csv = project.getFile("logger.csv");
InputStream initialContent = new ByteArrayInputStream("time,action".getBytes());
csv.create(initialContent, true, new NullProgressMonitor());
});
}
}
See also Eclipse Wiki, Vogella and the Javadoc for further details.

EAR classloader doesn't see ApplicationInsights.xml located in jar

I have the following problem: I try to deploy the Azure telemetry configuration in jar file that is located in the war file inside main ear. Unfortunately, during server startup some errors occur as ApplicationInsights.xml cannot be found. I debugged it and I found out that it cannot be read by com.microsoft.applicationinsights.internal.config.ConfigurationFileLocator.getConfigurationFile().
There is a structure of my module:
azure
|_ src
|_ main
|_ java
| |_ mypackage
| |_ MicrometerRegistryConfigurationListener.java
|_ resources
|_ ApplicationInsights.xml
There is MicrometerRegistryConfigurationListener.java:
#WebListener
public class MicrometerRegistryConfigurationListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
MeterRegistry azureMeterRegistry = new AzureMonitorMeterRegistry(new AzureMonitorConfig() {
#Override
public String get(String key) {
return null;
}
#Override
public Duration step() {
return Duration.ofSeconds(5);
}
}, Clock.SYSTEM);
new JvmThreadMetrics().bindTo(azureMeterRegistry);
new JvmMemoryMetrics().bindTo(azureMeterRegistry);
new JvmGcMetrics().bindTo(azureMeterRegistry);
servletContextEvent.getServletContext().setAttribute("AzureMonitorMeterRegistry", azureMeterRegistry);
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
And there is a structure of ear:
ear
|_ war
|_ WEB-INF/lib
|_ azure.jar
|_ ApplicationInsights.xml
Of course, azure.jar contains also classes, i.a. MicrometerRegistryConfigurationListener.
What is going on? Why ApplicationInsights.xml is not visible?
Cause jar is a file, it's not a folder. The loader tries to access a file system path, but it can not access a path in your JAR. So you could use the resource.getInputStream() method to retrieve it as a InputStream. You could get details from this doc:Java: Load file from classpath in Spring Boot.
Example code:
public class EnvResolver {
private ResourceLoader resourceLoader;
public EnvResolver(){
resourceLoader = new DefaultResourceLoader();
}
public List GetEnvConfig() throws IOException {
Yaml yaml = new Yaml();
Resource resource = resourceLoader.getResource("classpath:test-environment.yml");
System.out.println("FileName: " + resource.getFilename());
InputStream inputStream = resource.getInputStream();
Map<String, Map<String, String>> result = yaml.load(inputStream);
List<TestEnvVo> envList = new ArrayList<>();
for (String key : result.keySet()) {
TestEnvVo testEnvVo = new TestEnvVo();
testEnvVo.setEnvName(result.get(key).get("name"));
testEnvVo.setDomain(result.get(key).get("domain"));
envList.add(testEnvVo);
}
inputStream.close();
return envList;
}
}
After searching, I found another way to configure it, in your POM file add the resources configuration. You could have a try.
<resources>
<resource>
<directory>src/main/resource</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.tld</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
Hope this could help you.
UPDATE:
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
public class Hello {
public static void main(String[] args) throws IOException {
InputStream resourceInputStream = null;
URL resourceURL = Hello.class.getClassLoader().getResource("resources/hello.json");
if(resourceURL == null) {
System.out.println("Get the InputStream of file in IDE");
resourceInputStream = new FileInputStream(Hello.class.getClassLoader().getResource("").getPath()+"../../src/main/resources/hello.json");
} else {
System.out.println("Get the InputStream of file from runnable jar");
resourceInputStream = Hello.class.getClassLoader().getResourceAsStream("resources/hello.json");
}
System.out.println();
StringBuilder builder = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(resourceInputStream));
String line = null;
while((line = br.readLine()) != null) {
builder.append(line+"\n");
}
br.close();
System.out.println(builder.toString());
}
}

How to start a JavaFx OSGi bundle

I'm trying to run a JavaFx OSGi module, but I keep getting the error:
org.osgi.framework.BundleException: Unable to resolve OSGiDmHelloWorldProvider [7](R 7.0): missing requirement [OSGiDmHelloWorldProvider [7](R 7.0)] osgi.wiring.package; (osgi.wiring.package=javafx.application) Unresolved requirements: [[OSGiDmHelloWorldProvider [7](R 7.0)] osgi.wiring.package; (osgi.wiring.package=javafx.application)]
at org.apache.felix.framework.Felix.resolveBundleRevision(Felix.java:4112)
at org.apache.felix.framework.Felix.startBundle(Felix.java:2118)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:998)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:984)
This is how I load and start the modules:
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import java.io.File;
import java.util.*;
public class Launcher {
private static String[] libs = null;
private BundleContext context;
private Launcher() {
FrameworkFactory frameworkFactory = ServiceLoader.load(FrameworkFactory.class).iterator().next();
Map<String, String> config = new HashMap<String, String>();
config.put("osgi.console", "");
config.put("osgi.clean", "true");
config.put("osgi.noShutdown", "true");
config.put("eclipse.ignoreApp", "true");
config.put("osgi.bundles.defaultStartLevel", "4");
config.put("osgi.configuration.area", "./configuration");
// automated bundles deployment
config.put("felix.fileinstall.dir", "./dropins");
config.put("felix.fileinstall.noInitialDelay", "true");
config.put("felix.fileinstall.start.level", "4");
config.put(Constants.FRAMEWORK_BOOTDELEGATION, "javafx.*,com.sun.javafx.*");
config.put(Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_BUNDLE_PARENT_APP);
Framework framework = frameworkFactory.newFramework(config);
try {
framework.init();
framework.start();
} catch (BundleException e) {
e.printStackTrace();
}
context = framework.getBundleContext();
Bundle OSGiDmHelloWorldProvider = install("OSGiDmHelloWorldProvider");
try {
OSGiDmHelloWorldProvider.start();
} catch (BundleException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Launcher();
}
private String[] getLibs() {
if (libs == null) {
List<String> jarsList = new ArrayList<String>();
File pluginsDir = new File("libs");
System.out.println("PATHS : " + pluginsDir.getAbsolutePath());
for (String jar : pluginsDir.list()) {
jarsList.add(jar);
}
libs = jarsList.toArray(new String[jarsList.size()]);
}
return libs;
}
protected Bundle install(String name) {
String found = null;
for (String jar : getLibs()) {
if (jar.startsWith(name)) {
found = String.format("file:libs/%s", jar);
System.out.println(found);
break;
}
}
if (found == null) {
throw new RuntimeException(String.format("JAR for %s not found", name));
}
try {
return context.installBundle(found);
} catch (BundleException e) {
e.printStackTrace();
}
return null;
}
}
The OSGiDmHelloWorldProvider Activator class:
import com.bw.osgi.provider.able.HelloWorldService;
import com.bw.osgi.provider.impl.HelloWorldServiceImpl;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ProviderActivator implements BundleActivator {
Logger logger = LoggerFactory.getLogger(ProviderActivator.class);
Stage stage;
private ServiceRegistration registration;
BundleContext bundleContext;
#Override
public void start(BundleContext bundleContext) throws Exception {
registration = bundleContext.registerService(
HelloWorldService.class.getName(),
new HelloWorldServiceImpl(),
null);
Platform.runLater(new Runnable() {
#Override
public void run() {
stage = new Stage();
BorderPane pane = new BorderPane();
Scene scene = new Scene(pane, 400, 200);
pane.setCenter(new Label("This is a JavaFX Scene in a Stage"));
stage.setScene(scene);
stage.show();
}
});
System.out.println("STAGE CALLED !!!");
}
#Override
public void stop(BundleContext bundleContext) throws Exception {
registration.unregister();
logger.info("Set4Jfx Bundle: stop()");
Platform.runLater(new Runnable() {
#Override
public void run() {
stage.close();
}
});
}
}
How can I load a module that uses JavaFx in a Maven OSGi application?
The error message means that your bundle imports package javafx.application, and no bundle exports that package. Therefore the import cannot be resolved.
I note that in your launcher code you try to set bootdelegation to javafx.*. That might allow the class to be loaded from the boot classpath if your bundle ever got as far as running, however it cannot get that far because of the unresolved import.
If you intend for the JavaFX classes to be loaded from the boot classloader then you should remove the package import from your own bundle. You would also have to arrange for the JavaFX classes to actually be provided by the boot classloader, since AFAIK they are normally only visible from the extension classloader.
However a better solution would be leave the import alone, and arrange for the javafx.application package to be exported from a bundle. That could be done from the system bundle using Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA.
Update
You should probably look at the Drombler FX tool/framework from #Puce, since it sounds like he has already done a lot of these setup tasks. However I felt that his answer didn't directly address your question about why your code was failing.
I've released some first versions of Drombler FX - the modular application framework for JavaFX based on OSGi and Maven (POM first).
It takes care of initializing JavaFX and OSGi.
Maybe you find it useful. The application framework is Open Source and the code is available on GitHub.
There is also a tutorial with a Getting Started trail.

getting resources inside a built application

i want to get files inside a folder when my application is running, so i know that i need to get it as resouce, if i will get it as file, it wont work, so it what i did.
jaxbContext = JAXBContext.newInstance(Catalogo.class);
jaxbUnmarshaller = jaxbContext.createUnmarshaller();
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("catalogos/");
BufferedReader br = new BufferedReader(new InputStreamReader(resourceAsStream));
String line;
try {
while((line = br.readLine()) != null){
InputStream resourceAsStream1 = getClass().getClassLoader().getResourceAsStream("catalogos/"+line);
tempCat = (Catalogo) jaxbUnmarshaller.unmarshal(resourceAsStream1);
if(tempCat != null){
codigoCurso = String.valueOf(tempCat.getCourse().getId());
nomeDoCurso = dados.get(codigoCurso);
anoCatalogo = String.valueOf(tempCat.getAno());
if(nomeDoCurso == null){
dados.put(codigoCurso, tempCat.getCourse().getNome());
}
anos.add(anoCatalogo);
}
}
What i want to do is, get all files inside a folder (/catalogos/) and loop through and unmarshall each to an object so i will be able to access the property i need. So, when i run this with netbeans, works perfectly, but when i build and run the jar, i dont get the same result i've got using netbeans, i mean, the data is not where i expected.
The following example demonstrates how to get files from a directory in current runnable jar file and read these files contents.
Assume you have a NetBeans project with name "FolderTestApp". Do the following steps:
In your project root folder FolderTestApp\ create folder myFiles.
Copy your catalogos folder to the FolderTestApp\myFiles\
myFiles folder is necessary to preserve catalogos folder in your jar file structure when project jar is being generated. myFiles folder will disappear from jar file, but catalogos folder will remain.
If you don't do these steps, and place catalogos directly to the project folder (not as a child folder for myFiles), then your files from catalogos folder will be placed to the root of your jar file.
Add myFiles folder as a source folder in netbeans project properties.
Assume your property files contain the following contents:
file1.properties:
key11=value11
key12=value12
key13=value13
file2.properties:
key21=value21
key22=value22
key23=value23
Please note, that code below is not optimized. It is plain'n'dirty proof of concept to show, how to solve your task.
Add the following class to your project:
package folderapp;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.util.Properties;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class FolderTestApp {
public static void main(String[] args) throws URISyntaxException, IOException {
new FolderTestApp();
}
public FolderTestApp() throws URISyntaxException, IOException {
// determining the running jar file location
String jarFilePath = getClass().getProtectionDomain().
getCodeSource().getLocation().toURI().getPath();
// note, that the starting / is removed
// because zip entries won't start with this symbol
String zipEntryFolder = "catalogos/";
try (ZipInputStream zipInputStream
= new ZipInputStream(new FileInputStream(jarFilePath))) {
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
System.out.println("processing: " + zipEntry.getName());
if (zipEntry.getName().startsWith(zipEntryFolder)) {
// directory "catalogos" will appear as a zip-entry
// and we're checking this condition
if (!zipEntry.isDirectory()) {
// adding symbol / because it is required for getResourceAsStream() call
printProperties("/" + zipEntry.getName());
}
}
zipEntry = zipInputStream.getNextEntry();
}
}
}
public void printProperties(String path) throws IOException {
try (InputStream is = getClass().getResourceAsStream(path)) {
InputStreamReader fr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(fr);
Properties properties = new Properties();
properties.load(br);
System.out.println("contents from: " + path + "\n");
Set<Object> keySet = properties.keySet();
for (Object key : keySet) {
System.out.println(key + " = " + properties.get(key));
}
System.out.println("---------------------------------------");
}
}
}
Set this class as the main class in your project settings (Run section).
And build your project via menu: Run - Build.
As your project has been built, open FolderTestApp/dist folder, where your generated jar is located and run this jar file:
That's it :)

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

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);
}
}

Categories