From ecf5245ae60a5769e53b444efcb15c988fd70f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereda?= Date: Fri, 25 Oct 2024 20:48:46 +0200 Subject: [PATCH] Add ModuleExplorer to explore custom controls in modules (#778) --- .../kit/editor/panel/library/LibraryUtil.java | 18 +++++ .../library/user/LibraryFolderWatcher.java | 68 +++++++++--------- .../kit/library/util/ModuleExplorer.java | 70 +++++++++++++++++++ .../kit/i18n/SceneBuilderKit.properties | 2 + 4 files changed, 122 insertions(+), 36 deletions(-) create mode 100644 kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/util/ModuleExplorer.java diff --git a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryUtil.java b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryUtil.java index c5013b5f2..f31bdfbe6 100644 --- a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryUtil.java +++ b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/editor/panel/library/LibraryUtil.java @@ -35,20 +35,38 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.module.ModuleReference; +import java.lang.module.ResolvedModule; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Locale; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; public class LibraryUtil { public static final String FOLDERS_LIBRARY_FILENAME = "library.folders"; //NOI18N + private static Set MODULES; LibraryUtil() { // no-op } + public static Optional getModuleReference(Path path) { + if (path == null) { + return Optional.empty(); + } + if (MODULES == null) { + MODULES = ModuleLayer.boot().configuration().modules(); + } + return MODULES.stream() + .map(ResolvedModule::reference) + .filter(r -> path.equals(r.location().map(Path::of).orElse(null))) + .findFirst(); + } + public static boolean isJarPath(Path path) { final String pathString = path.toString().toLowerCase(Locale.ROOT); return pathString.endsWith(".jar"); //NOI18N diff --git a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/user/LibraryFolderWatcher.java b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/user/LibraryFolderWatcher.java index 13984a47a..e2463bbf0 100644 --- a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/user/LibraryFolderWatcher.java +++ b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/user/LibraryFolderWatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Gluon and/or its affiliates. + * Copyright (c) 2017, 2024, Gluon and/or its affiliates. * Copyright (c) 2012, 2014, Oracle and/or its affiliates. * All rights reserved. Use is subject to license terms. * @@ -36,6 +36,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.LineNumberReader; +import java.lang.module.ModuleReference; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -53,6 +54,7 @@ import java.util.Date; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -66,6 +68,7 @@ import com.oracle.javafx.scenebuilder.kit.library.util.JarExplorer; import com.oracle.javafx.scenebuilder.kit.library.util.JarReport; import com.oracle.javafx.scenebuilder.kit.library.util.JarReportEntry; +import com.oracle.javafx.scenebuilder.kit.library.util.ModuleExplorer; /** * @@ -348,49 +351,55 @@ private LibraryItem makeLibraryItem(Path path) throws IOException { } - private void exploreAndUpdateLibrary(Collection jarsOrFolders) throws IOException { + private void exploreAndUpdateLibrary(Collection modulesOrJarsOrFolders) throws IOException { // 1) we create a classloader - // 2) we explore all the jars and folders + // 2) we explore all the modules, jars, and folders // 3) we construct a list of library items // 4) we update the user library with the class loader and items // 5) on startup only, we allow opening files that may/may not rely on the user library // 1) final ClassLoader classLoader; - if (jarsOrFolders.isEmpty()) { + if (modulesOrJarsOrFolders.isEmpty()) { classLoader = null; } else { - classLoader = new URLClassLoader(makeURLArrayFromPaths(jarsOrFolders)); + classLoader = new URLClassLoader(makeURLArrayFromPaths(modulesOrJarsOrFolders)); } // 2) - final List jarOrFolderReports = new ArrayList<>(); -// boolean shouldShowImportGluonJarAlert = false; - for (Path currentJarOrFolder : jarsOrFolders) { - String jarName = currentJarOrFolder.getName(currentJarOrFolder.getNameCount() - 1).toString(); + final List moduleOrJarOrFolderReports = new ArrayList<>(); + for (Path currentModuleOrJarOrFolder : modulesOrJarsOrFolders) { + String jarName = currentModuleOrJarOrFolder.getName(currentModuleOrJarOrFolder.getNameCount() - 1).toString(); if (JAVAFX_MODULES.stream().anyMatch(jarName::startsWith)) { continue; } JarReport jarReport; String resultText = ""; - if (LibraryUtil.isJarPath(currentJarOrFolder)) { - LOGGER.info(I18N.getString("log.info.explore.jar", currentJarOrFolder)); - final JarExplorer explorer = new JarExplorer(currentJarOrFolder); + Optional moduleReference = LibraryUtil.getModuleReference(currentModuleOrJarOrFolder); + if (moduleReference.isPresent()) { + LOGGER.info(I18N.getString("log.info.explore.module", moduleReference.get().descriptor())); + final ModuleExplorer explorer = new ModuleExplorer(moduleReference.get()); + jarReport = explorer.explore(); + resultText = I18N.getString("log.info.explore.module.results", jarName); + } + else if (LibraryUtil.isJarPath(currentModuleOrJarOrFolder)) { + LOGGER.info(I18N.getString("log.info.explore.jar", currentModuleOrJarOrFolder)); + final JarExplorer explorer = new JarExplorer(currentModuleOrJarOrFolder); jarReport = explorer.explore(classLoader); resultText = I18N.getString("log.info.explore.jar.results", jarName); } - else if (Files.isDirectory(currentJarOrFolder)) { - LOGGER.info(I18N.getString("log.info.explore.folder", currentJarOrFolder)); - final FolderExplorer explorer = new FolderExplorer(currentJarOrFolder); + else if (Files.isDirectory(currentModuleOrJarOrFolder)) { + LOGGER.info(I18N.getString("log.info.explore.folder", currentModuleOrJarOrFolder)); + final FolderExplorer explorer = new FolderExplorer(currentModuleOrJarOrFolder); jarReport = explorer.explore(classLoader); resultText = I18N.getString("log.info.explore.folder.results", jarName); } else { continue; } - jarOrFolderReports.add(jarReport); + moduleOrJarOrFolderReports.add(jarReport); StringBuilder sb = new StringBuilder(resultText).append("\n"); if (jarReport.getEntries().isEmpty()) { @@ -400,26 +409,13 @@ else if (Files.isDirectory(currentJarOrFolder)) { } LOGGER.info(sb.toString()); - LOGGER.info(I18N.getString("log.info.explore.end", currentJarOrFolder)); - - // if (jarReport.hasGluonControls()) { -// // We check if the jar has already been imported to avoid showing the import gluon jar -// // alert every time Scene Builder starts for jars that have already been imported -// if (!hasGluonJarBeenImported(jarReport.getJar().getFileName().toString())) { -// shouldShowImportGluonJarAlert = true; -// } -// -// } + LOGGER.info(I18N.getString("log.info.explore.end", currentModuleOrJarOrFolder)); } -// if (shouldShowImportGluonJarAlert && onImportingGluonControls != null) { -// onImportingGluonControls.run(); -// } - // 3) final List newItems = new ArrayList<>(); - for (JarReport jarOrFolderReport : jarOrFolderReports) { - newItems.addAll(makeLibraryItems(jarOrFolderReport)); + for (JarReport moduleOrJarOrFolderReport : moduleOrJarOrFolderReports) { + newItems.addAll(makeLibraryItems(moduleOrJarOrFolderReport)); } // 4) @@ -429,8 +425,8 @@ else if (Files.isDirectory(currentJarOrFolder)) { .stream() .distinct() .collect(Collectors.toList())); - library.updateJarReports(new ArrayList<>(jarOrFolderReports)); - library.getOnFinishedUpdatingJarReports().accept(jarOrFolderReports); + library.updateJarReports(new ArrayList<>(moduleOrJarOrFolderReports)); + library.getOnFinishedUpdatingJarReports().accept(moduleOrJarOrFolderReports); library.updateExplorationDate(new Date()); // 5 @@ -471,9 +467,9 @@ private URL[] makeURLArrayFromPaths(Collection paths) { if (url.toString().endsWith(".jar")) { result[i++] = new URL("jar", "", url + "!/"); // <-- jar:file/path/to/jar!/ } else { - result[i++] = url; // <-- file:/path/to/folder/ + result[i++] = url; // <-- file:/path/to/folder/ or jrt:/module.name } - } catch(MalformedURLException x) { + } catch (MalformedURLException x) { throw new RuntimeException("Bug in " + getClass().getSimpleName(), x); //NOI18N } } diff --git a/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/util/ModuleExplorer.java b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/util/ModuleExplorer.java new file mode 100644 index 000000000..b6940bab0 --- /dev/null +++ b/kit/src/main/java/com/oracle/javafx/scenebuilder/kit/library/util/ModuleExplorer.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024, Gluon and/or its affiliates. + * All rights reserved. Use is subject to license terms. + * + * This file is available and licensed under the following license: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the distribution. + * - Neither the name of Oracle Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.oracle.javafx.scenebuilder.kit.library.util; + +import com.oracle.javafx.scenebuilder.kit.library.util.JarReportEntry.Status; + +import java.io.IOException; +import java.lang.module.ModuleReader; +import java.lang.module.ModuleReference; +import java.nio.file.Path; + +public class ModuleExplorer extends ExplorerBase { + + private final ModuleReference moduleReference; + private final Module module; + + public ModuleExplorer(ModuleReference moduleReference) { + assert moduleReference != null; + this.moduleReference = moduleReference; + this.module = ModuleLayer.boot().findModule(moduleReference.descriptor().name()).orElseThrow(); + } + + public JarReport explore() throws IOException { + ClassLoader classLoader = module.getClassLoader(); + final JarReport result = new JarReport(moduleReference.location().map(Path::of).orElse(null)); + try (ModuleReader reader = moduleReference.open()) { + reader.list().forEach(cl -> { + if (cl.endsWith(".class")) { + String className = cl.substring(0, cl.length() - ".class".length()).replaceAll("/", "."); + JarReportEntry explored = super.exploreEntry(cl, classLoader, className); + if (explored.getStatus() != Status.IGNORED) { + result.getEntries().add(explored); + } + } + }); + } + + return result; + } + +} diff --git a/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/i18n/SceneBuilderKit.properties b/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/i18n/SceneBuilderKit.properties index f80e38b36..98e395b64 100644 --- a/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/i18n/SceneBuilderKit.properties +++ b/kit/src/main/resources/com/oracle/javafx/scenebuilder/kit/i18n/SceneBuilderKit.properties @@ -71,8 +71,10 @@ label.qualifier.vertical = (vertical) # ----------------------------------------------------------------------------- log.info.explore.end = End exploring {0} log.info.explore.folder = Start exploring FOLDER {0} +log.info.explore.module = Start exploring MODULE {0} log.info.explore.jar = Start exploring JAR {0} log.info.explore.jar.results = Results of exploring JAR {0} +log.info.explore.module.results = Results of exploring MODULE {0} log.info.explore.folder.results = Results of exploring FOLDER {0} log.info.explore.no.results = No custom controls found log.warning.inline.edit.internationalized.strings = Can''t inline edit internationalized strings