diff --git a/core/framework/src/main/java/org/phoebus/framework/preferences/PropertyPreferenceWriter.java b/core/framework/src/main/java/org/phoebus/framework/preferences/PropertyPreferenceWriter.java index 0f39f4392e..310df03199 100644 --- a/core/framework/src/main/java/org/phoebus/framework/preferences/PropertyPreferenceWriter.java +++ b/core/framework/src/main/java/org/phoebus/framework/preferences/PropertyPreferenceWriter.java @@ -7,10 +7,27 @@ *******************************************************************************/ package org.phoebus.framework.preferences; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** Write preferences in property file format * @author Kay Kasemir @@ -36,31 +53,149 @@ public static void save(final OutputStream stream) throws Exception final OutputStreamWriter out = new OutputStreamWriter(stream); ) { - out.append("# Preference settings\n"); - out.append("# Format:\n"); - out.append("# the.package.name/key=value\n"); - listSettings(out, Preferences.userRoot()); - out.append("# End.\n"); + out.append("# Preference settings
\n"); + out.append("# Format:
\n"); + out.append("# the.package.name/key=value
\n"); + out.append("
# key=value in red are incorrect properties

\n"); + listSettings(getAllPropertyKeys(), out, Preferences.userRoot()); + out.append("# End.
\n"); out.flush(); } } - private static void listSettings(final Writer out, final Preferences node) throws Exception + private static void listSettings(Map allKeysWithPackages, final Writer out, final Preferences node) throws Exception { for (String key : node.keys()) - formatSetting(out, node, key); + formatSetting(allKeysWithPackages, out, node, key); for (String child : node.childrenNames()) - listSettings(out, node.node(child)); + listSettings(allKeysWithPackages, out, node.node(child)); } - private static void formatSetting(final Writer out, final Preferences node, final String key) throws Exception + private static void formatSetting(Map allKeysWithPackages, final Writer out, final Preferences node, final String key) throws Exception { final String path = node.absolutePath(); - out.append(path.substring(1).replace('/', '.')) - .append('/') - .append(key) + String fullKey = path.substring(1).replace('/', '.') + '/' + key; + String keyFound = allKeysWithPackages.get(fullKey); + boolean bNotFound = keyFound == null ? true : false; + if (bNotFound) out.append("
"); + out.append(escapeHtml(fullKey)) .append('=') - .append(node.get(key, "")) - .append('\n'); + .append(escapeHtml(node.get(key, ""))) + .append("
\n"); + if (bNotFound) out.append("
"); + } + + private static Map getAllPropertyKeys() throws Exception + { + Map allKeysWithPackages = new HashMap<>(); + + String classpath = System.getProperty("java.class.path"); + String[] jars = classpath.split(System.getProperty("path.separator")); + + if (jars.length == 1) jars = getAllJarFromManifest(jars[0]); + + for (String jarEntry : jars) { + File file = new File(jarEntry); + + if (jarEntry.endsWith(".jar")) { + try (JarFile jarFile = new JarFile(file)) { + Enumeration entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + + if (entryName.endsWith("preferences.properties")) { + parsePropertiesWithPackage( + jarFile.getInputStream(entry), + entryName, + allKeysWithPackages + ); + } + } + } catch (IOException e) { + System.err.println("Error opening JAR : " + jarEntry); + e.printStackTrace(); + } + } + } + + return allKeysWithPackages; + } + + private static String[] getAllJarFromManifest(String jarPath) { + String[] jars = new String[0]; + File jarFile = new File(jarPath); + + try (JarFile jar = new JarFile(jarFile)) { + Manifest manifest = jar.getManifest(); + + if (manifest != null) { + String classPath = manifest.getMainAttributes().getValue("Class-Path"); + + if (classPath != null && !classPath.isEmpty()) { + jars = classPath.split(" "); + + for (int iJar = 0; iJar < jars.length; iJar++) { + Path fullPath = Paths.get(jarFile.getParent()).resolve(jars[iJar]); + jars[iJar] = fullPath.toString(); + } + } else { + System.err.println("No Class-Path found in MANIFEST.MF."); + } + } else { + System.err.println("MANIFEST.MF not found in the JAR."); + } + } catch (IOException e) { + System.err.println("Error when reading the jar : " + jarPath); + e.printStackTrace(); + } + + return jars; + } + + private static void parsePropertiesWithPackage(InputStream inputStream, String fileName, Map allKeysWithPackages) { + Properties props = new Properties(); + String packageName = null; + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + String line; + StringBuilder content = new StringBuilder(); + + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.startsWith("#") && line.contains("Package")) { + // Find package name + Pattern pattern = Pattern.compile("#\\s*Package\\s+([^\\s]+)"); + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + packageName = matcher.group(1); + } + } else if (!line.startsWith("#")) { + content.append(line).append("\n"); + } + } + + if (content.length() > 0) { + props.load(new ByteArrayInputStream(content.toString().getBytes())); + } + + // properties found + for (String key : props.stringPropertyNames()) { + String prefixedKey = (packageName != null) ? packageName + "/" + key : key; + allKeysWithPackages.put(prefixedKey, props.getProperty(key)); + } + } catch (IOException e) { + System.err.println("Error when reading file " + fileName); + e.printStackTrace(); + } + } + + private static String escapeHtml(String input) { + return input.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); } } diff --git a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java index 0add9484a5..254d9d48d5 100644 --- a/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java +++ b/core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java @@ -42,6 +42,7 @@ import javafx.scene.image.Image; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; +import javafx.scene.web.WebView; /** Menu entry to open 'about' * @author Kay Kasemir @@ -204,12 +205,17 @@ private Node createDetailSection() logger.log(Level.WARNING, "Cannot list preferences", ex); } - area = new TextArea(prefs_buf.toString()); - area.setEditable(false); + WebView webView = new WebView(); + String content = ""; + content += prefs_buf.toString(); + content += ""; + webView.getEngine().loadContent(content); - VBox.setVgrow(area, Priority.ALWAYS); + VBox.setVgrow(webView, Priority.ALWAYS); - final Tab prefs = new Tab(Messages.HelpAboutPrefs, area); + final Tab prefs = new Tab(Messages.HelpAboutPrefs, webView); final TabPane tabs = new TabPane(apps, envs, props, prefs); return tabs;