77 *******************************************************************************/
88package org .phoebus .framework .preferences ;
99
10+ import java .io .BufferedReader ;
11+ import java .io .ByteArrayInputStream ;
12+ import java .io .File ;
13+ import java .io .IOException ;
14+ import java .io .InputStream ;
15+ import java .io .InputStreamReader ;
1016import java .io .OutputStream ;
1117import java .io .OutputStreamWriter ;
1218import java .io .Writer ;
19+ import java .nio .file .Files ;
20+ import java .nio .file .Path ;
21+ import java .nio .file .Paths ;
22+ import java .util .*;
23+ import java .util .jar .JarEntry ;
24+ import java .util .jar .JarFile ;
25+ import java .util .jar .Manifest ;
26+ import java .util .logging .Level ;
27+ import java .util .logging .Logger ;
1328import java .util .prefs .Preferences ;
29+ import java .util .regex .Matcher ;
30+ import java .util .regex .Pattern ;
31+ import java .util .stream .Collectors ;
32+ import java .util .stream .Stream ;
1433
1534/** Write preferences in property file format
1635 * @author Kay Kasemir
1736 */
1837@ SuppressWarnings ("nls" )
1938public class PropertyPreferenceWriter
2039{
40+ public static final Logger logger = Logger .getLogger (PropertyPreferenceWriter .class .getName ());
41+ public static Set <String > excludedKeys = new HashSet <>();
42+ public static Set <String > excludedPackages = new HashSet <>();
43+
2144 /** Save preferences in property file format
2245 *
2346 * <p>Properties have the name "package/setting",
@@ -31,36 +54,185 @@ public class PropertyPreferenceWriter
3154 */
3255 public static void save (final OutputStream stream ) throws Exception
3356 {
57+ Map <String , String > allKeysWithPackages = getAllPropertyKeys ();
58+ Preferences prefs = Preferences .userRoot ().node ("org/phoebus/ui" );
59+
60+ String value = prefs .get ("excluded_keys_from_settings_check" , "" );
61+ if (value .isEmpty ()) value = allKeysWithPackages .get ("org.phoebus.ui/excluded_keys_from_settings_check" );
62+ if (!value .isEmpty ()) excludedKeys = Arrays .stream (value .split ("," )).map (String ::trim ).collect (Collectors .toSet ());
63+
64+ value = prefs .get ("excluded_packages_from_settings_check" , "" );
65+ if (value .isEmpty ()) value = allKeysWithPackages .get ("org.phoebus.ui/excluded_packages_from_settings_check" );
66+ if (!value .isEmpty ()) excludedPackages = Arrays .stream (value .split ("," )).map (String ::trim ).collect (Collectors .toSet ());
67+
3468 try
3569 (
36- final OutputStreamWriter out = new OutputStreamWriter (stream );
70+ final OutputStreamWriter out = new OutputStreamWriter (stream )
3771 )
3872 {
39- out .append ("# Preference settings\n " );
40- out .append ("# Format:\n " );
41- out .append ("# the.package.name/key=value\n " );
42- listSettings (out , Preferences .userRoot ());
43- out .append ("# End.\n " );
73+ out .append ("# Preference settings<br/>\n " );
74+ out .append ("# Format:<br/>\n " );
75+ out .append ("# the.package.name/key=value<br/>\n " );
76+ out .append ("<div style='color: red; font-weight: bold'># key=value in red are incorrect properties</div><br/>\n " );
77+ listSettings (allKeysWithPackages , out , Preferences .userRoot ());
78+ out .append ("<br/>\n " );
79+ out .append ("# End.<br/>\n " );
4480 out .flush ();
4581 }
4682 }
4783
48- private static void listSettings (final Writer out , final Preferences node ) throws Exception
84+ private static void listSettings (Map < String , String > allKeysWithPackages , final Writer out , final Preferences node ) throws Exception
4985 {
5086 for (String key : node .keys ())
51- formatSetting (out , node , key );
87+ formatSetting (allKeysWithPackages , out , node , key );
5288 for (String child : node .childrenNames ())
53- listSettings (out , node .node (child ));
89+ listSettings (allKeysWithPackages , out , node .node (child ));
5490 }
5591
56- private static void formatSetting (final Writer out , final Preferences node , final String key ) throws Exception
92+ private static void formatSetting (Map < String , String > allKeysWithPackages , final Writer out , final Preferences node , final String key ) throws Exception
5793 {
5894 final String path = node .absolutePath ();
59- out .append (path .substring (1 ).replace ('/' , '.' ))
60- .append ('/' )
61- .append (key )
95+ String fullKey = path .substring (1 ).replace ('/' , '.' ) + '/' + key ;
96+ String keyFound = allKeysWithPackages .get (fullKey );
97+ boolean bNotFound = keyFound == null ;
98+
99+ // exclude keys that must not be checked
100+ boolean containsExcludedKeys = excludedKeys .stream ().anyMatch (key ::contains );
101+ boolean containsExcludedPackages = excludedPackages .stream ().anyMatch (fullKey ::startsWith );
102+ if (containsExcludedKeys || containsExcludedPackages ) bNotFound = false ;
103+
104+ if (bNotFound ) out .append ("<div style='color: red; font-weight: bold'>" );
105+ out .append (escapeHtml (fullKey ))
62106 .append ('=' )
63- .append (node .get (key , "" ))
64- .append ('\n' );
107+ .append (escapeHtml (node .get (key , "" )))
108+ .append ("<br/>\n " );
109+ if (bNotFound ) out .append ("</div>" );
110+ }
111+
112+ private static Map <String , String > getAllPropertyKeys ()
113+ {
114+ Map <String , String > allKeysWithPackages = new HashMap <>();
115+
116+ String classpath = System .getProperty ("java.class.path" );
117+ String [] jars = classpath .split (File .pathSeparator );
118+
119+ if (jars .length == 1 ) jars = getAllJarFromManifest (jars [0 ]);
120+
121+ for (String jarEntry : jars ) {
122+ if (jarEntry .endsWith (".jar" )) {
123+ File file = new File (jarEntry );
124+ try (JarFile jarFile = new JarFile (file )) {
125+ Enumeration <JarEntry > entries = jarFile .entries ();
126+
127+ while (entries .hasMoreElements ()) {
128+ JarEntry entry = entries .nextElement ();
129+ String entryName = entry .getName ();
130+
131+ if (entryName .endsWith ("preferences.properties" )) {
132+ parsePropertiesWithPackage (
133+ jarFile .getInputStream (entry ),
134+ entryName ,
135+ allKeysWithPackages
136+ );
137+ }
138+ }
139+ } catch (IOException ex ) {
140+ logger .log (Level .WARNING , "Error opening JAR : " + jarEntry , ex );
141+ }
142+ }
143+ else if (jarEntry .endsWith ("classes" )) {
144+ Path startPath = Paths .get (jarEntry );
145+ String filePattern = "preferences.properties" ;
146+
147+ try (Stream <Path > paths = Files .walk (startPath )) {
148+ paths .filter (path -> path .toString ().endsWith (filePattern ))
149+ .forEach (path -> {
150+ try (InputStream inputStream = Files .newInputStream (path )) {
151+ parsePropertiesWithPackage (inputStream , path .getFileName ().toString (), allKeysWithPackages );
152+ } catch (IOException ex ) {
153+ logger .log (Level .WARNING , "Error opening properties file : " + path , ex );
154+ }
155+ });
156+ } catch (IOException ex ) {
157+ logger .log (Level .WARNING , "Error listing files in : " + startPath , ex );
158+ }
159+ }
160+ }
161+
162+ return allKeysWithPackages ;
163+ }
164+
165+ private static String [] getAllJarFromManifest (String jarPath ) {
166+ String [] jars = new String [0 ];
167+ File jarFile = new File (jarPath );
168+
169+ try (JarFile jar = new JarFile (jarFile )) {
170+ Manifest manifest = jar .getManifest ();
171+
172+ if (manifest != null ) {
173+ String classPath = manifest .getMainAttributes ().getValue ("Class-Path" );
174+
175+ if (classPath != null && !classPath .isEmpty ()) {
176+ jars = classPath .split (" " );
177+
178+ for (int iJar = 0 ; iJar < jars .length ; iJar ++) {
179+ Path fullPath = Paths .get (jarFile .getParent ()).resolve (jars [iJar ]);
180+ jars [iJar ] = fullPath .toString ();
181+ }
182+ } else {
183+ logger .log (Level .WARNING , "No Class-Path found in MANIFEST.MF " + jarPath );
184+ }
185+ } else {
186+ logger .log (Level .WARNING , "MANIFEST.MF not found in the JAR " + jarPath );
187+ }
188+ } catch (IOException ex ) {
189+ logger .log (Level .WARNING , "Error when reading the jar : " + jarPath , ex );
190+ }
191+
192+ return jars ;
193+ }
194+
195+ private static void parsePropertiesWithPackage (InputStream inputStream , String fileName , Map <String , String > allKeysWithPackages ) {
196+ Properties props = new Properties ();
197+ String packageName = null ;
198+
199+ try (BufferedReader reader = new BufferedReader (new InputStreamReader (inputStream ))) {
200+ String line ;
201+ StringBuilder content = new StringBuilder ();
202+
203+ while ((line = reader .readLine ()) != null ) {
204+ line = line .trim ();
205+ if (line .startsWith ("#" ) && line .contains ("Package" )) {
206+ // Find package name
207+ Pattern pattern = Pattern .compile ("#\\ s*Package\\ s+(\\ S+)" );
208+ Matcher matcher = pattern .matcher (line );
209+ if (matcher .find ()) {
210+ packageName = matcher .group (1 );
211+ }
212+ } else if (!line .startsWith ("#" )) {
213+ content .append (line ).append ("\n " );
214+ }
215+ }
216+
217+ if (!content .isEmpty ()) {
218+ props .load (new ByteArrayInputStream (content .toString ().getBytes ()));
219+ }
220+
221+ // properties found
222+ for (String key : props .stringPropertyNames ()) {
223+ String prefixedKey = (packageName != null ) ? packageName + "/" + key : key ;
224+ allKeysWithPackages .put (prefixedKey , props .getProperty (key ));
225+ }
226+ } catch (IOException ex ) {
227+ logger .log (Level .WARNING , "Error when reading file " + fileName , ex );
228+ }
229+ }
230+
231+ private static String escapeHtml (String input ) {
232+ return input .replace ("&" , "&" )
233+ .replace ("<" , "<" )
234+ .replace (">" , ">" )
235+ .replace ("\" " , """ )
236+ .replace ("'" , "'" );
65237 }
66238}
0 commit comments