Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,40 @@
*******************************************************************************/
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.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/** Write preferences in property file format
* @author Kay Kasemir
*/
@SuppressWarnings("nls")
public class PropertyPreferenceWriter
{
public static final Logger logger = Logger.getLogger(PropertyPreferenceWriter.class.getName());
public static Set<String> excludedKeys = new HashSet<>();
public static Set<String> excludedPackages = new HashSet<>();

/** Save preferences in property file format
*
* <p>Properties have the name "package/setting",
Expand All @@ -31,36 +54,185 @@ public class PropertyPreferenceWriter
*/
public static void save(final OutputStream stream) throws Exception
{
Map<String, String> allKeysWithPackages = getAllPropertyKeys();
Preferences prefs = Preferences.userRoot().node("org/phoebus/ui");

String value = prefs.get("excluded_keys_from_settings_check", "");
if (value.isEmpty()) value = allKeysWithPackages.get("org.phoebus.ui/excluded_keys_from_settings_check");
if (!value.isEmpty()) excludedKeys = Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toSet());

value = prefs.get("excluded_packages_from_settings_check", "");
if (value.isEmpty()) value = allKeysWithPackages.get("org.phoebus.ui/excluded_packages_from_settings_check");
if (!value.isEmpty()) excludedPackages = Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toSet());

try
(
final OutputStreamWriter out = new OutputStreamWriter(stream);
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<br/>\n");
out.append("# Format:<br/>\n");
out.append("# the.package.name/key=value<br/>\n");
out.append("<div style='color: red; font-weight: bold'># key=value in red are incorrect properties</div><br/>\n");
listSettings(allKeysWithPackages, out, Preferences.userRoot());
out.append("<br/>\n");
out.append("# End.<br/>\n");
out.flush();
}
}

private static void listSettings(final Writer out, final Preferences node) throws Exception
private static void listSettings(Map<String, String> 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<String, String> 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;

// exclude keys that must not be checked
boolean containsExcludedKeys = excludedKeys.stream().anyMatch(key::contains);
boolean containsExcludedPackages = excludedPackages.stream().anyMatch(fullKey::startsWith);
if (containsExcludedKeys || containsExcludedPackages) bNotFound = false;

if (bNotFound) out.append("<div style='color: red; font-weight: bold'>");
out.append(escapeHtml(fullKey))
.append('=')
.append(node.get(key, ""))
.append('\n');
.append(escapeHtml(node.get(key, "")))
.append("<br/>\n");
if (bNotFound) out.append("</div>");
}

private static Map<String, String> getAllPropertyKeys()
{
Map<String, String> allKeysWithPackages = new HashMap<>();

String classpath = System.getProperty("java.class.path");
String[] jars = classpath.split(File.pathSeparator);

if (jars.length == 1) jars = getAllJarFromManifest(jars[0]);

for (String jarEntry : jars) {
if (jarEntry.endsWith(".jar")) {
File file = new File(jarEntry);
try (JarFile jarFile = new JarFile(file)) {
Enumeration<JarEntry> 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 ex) {
logger.log(Level.WARNING, "Error opening JAR : " + jarEntry, ex);
}
}
else if (jarEntry.endsWith("classes")) {
Path startPath = Paths.get(jarEntry);
String filePattern = "preferences.properties";

try (Stream<Path> paths = Files.walk(startPath)) {
paths.filter(path -> path.toString().endsWith(filePattern))
.forEach(path -> {
try (InputStream inputStream = Files.newInputStream(path)) {
parsePropertiesWithPackage(inputStream, path.getFileName().toString(), allKeysWithPackages);
} catch (IOException ex) {
logger.log(Level.WARNING, "Error opening properties file : " + path, ex);
}
});
} catch (IOException ex) {
logger.log(Level.WARNING, "Error listing files in : " + startPath, ex);
}
}
}

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 {
logger.log(Level.WARNING, "No Class-Path found in MANIFEST.MF " + jarPath);
}
} else {
logger.log(Level.WARNING, "MANIFEST.MF not found in the JAR " + jarPath);
}
} catch (IOException ex) {
logger.log(Level.WARNING, "Error when reading the jar : " + jarPath, ex);
}

return jars;
}

private static void parsePropertiesWithPackage(InputStream inputStream, String fileName, Map<String, String> 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.isEmpty()) {
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 ex) {
logger.log(Level.WARNING, "Error when reading file " + fileName, ex);
}
}

private static String escapeHtml(String input) {
return input.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}
}
14 changes: 10 additions & 4 deletions core/ui/src/main/java/org/phoebus/ui/help/OpenAbout.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = "<html><head><style>" +
"body {font-family: monospace;}" +
"</style></head><body>";
content += prefs_buf.toString();
content += "</body></html>";
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;
Expand Down
4 changes: 4 additions & 0 deletions core/ui/src/main/resources/phoebus_ui_preferences.properties
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,7 @@ default_window_title=CS-Studio
# For a system file use syntax; 'file:</path/to/custom.css>'
# For a file served over http use syntax: 'http://<address:port/custom.css>'
custom_css_styling=

# Keywords that can be excluded from the settings check in the About Dialog
excluded_keys_from_settings_check=external_app,password,username
excluded_packages_from_settings_check=eu.ess,fr.cea