javafx: Inspection of FXML attributes and tags which contain default values and there...
[idea/community.git] / plugins / javaFX / javaFX-CE / testSrc / org / jetbrains / plugins / javaFX / fxml / JavaFxGenerateDefaultPropertyValuesScript.java
1 package org.jetbrains.plugins.javaFX.fxml;
2
3 import javafx.application.Application;
4 import javafx.application.Platform;
5 import javafx.scene.Scene;
6 import javafx.scene.control.Button;
7 import javafx.scene.layout.StackPane;
8 import javafx.stage.Stage;
9 import org.jetbrains.annotations.NotNull;
10
11 import java.beans.*;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.lang.reflect.*;
16 import java.util.*;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19 import java.util.zip.ZipEntry;
20 import java.util.zip.ZipInputStream;
21
22 /**
23  * This is not a test, this is a config generator for JavaFxRedundantPropertyValueInspection
24  * <p>
25  * When launched without arguments it produces default values for JavaFX classes having default constructor and their superclasses, including some (but not all) abstract classes
26  * <p>
27  * When launched with <code>-fromSource</code> argument it attempts to extract default property values from the sources (JavaDoc and declarations),
28  * the results can be used for updating the contents of {@link #ourFromSource} map, which contains manually edited properties
29  *
30  * @author Pavel.Dolgov
31  */
32 public class JavaFxGenerateDefaultPropertyValuesScript extends Application {
33   public static final String BINARIES_PATH = "/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar";
34   public static final String SOURCE_PATH = "/usr/lib/jvm/java-8-oracle/javafx-src.zip";
35
36   public static void main(String[] args) {
37     if (args.length == 1 && "-fromSource".equals(args[0])) {
38       scanSource();
39     }
40     else {
41       launch(args);
42     }
43   }
44
45   @Override
46   public void start(Stage primaryStage) throws Exception {
47     Button b = new Button();
48     b.setText("Generate");
49     b.setOnAction(event -> generate());
50
51     StackPane pane = new StackPane();
52     pane.getChildren().add(b);
53     primaryStage.setScene(new Scene(pane, 300, 200));
54     primaryStage.show();
55
56     Platform.runLater(() -> {
57       generate();
58       primaryStage.close();
59     });
60   }
61
62   private static final Map<Class, Class> ourToBoxed = new HashMap<>();
63
64   static {
65     ourToBoxed.put(Boolean.TYPE, Boolean.class);
66     ourToBoxed.put(Character.TYPE, Character.class);
67     ourToBoxed.put(Byte.TYPE, Byte.class);
68     ourToBoxed.put(Short.TYPE, Short.class);
69     ourToBoxed.put(Integer.TYPE, Integer.class);
70     ourToBoxed.put(Long.TYPE, Long.class);
71     ourToBoxed.put(Float.TYPE, Float.class);
72     ourToBoxed.put(Double.TYPE, Double.class);
73   }
74
75   private static final Map<String, String> ourFromSource = new TreeMap<>();
76
77   static {
78     ourFromSource.put("javafx.concurrent.ScheduledService#maximumFailureCount", "Integer=2147483647");
79     ourFromSource.put("javafx.concurrent.ScheduledService#restartOnFailure", "Boolean=true");
80     ourFromSource.put("javafx.scene.Node#accessibleRole", "Enum=NODE");
81     ourFromSource.put("javafx.scene.Node#focusTraversable", "Boolean=false");
82     ourFromSource.put("javafx.scene.Node#nodeOrientation", "Enum=INHERIT");
83     ourFromSource.put("javafx.scene.Node#pickOnBounds", "Boolean=false");
84     ourFromSource.put("javafx.scene.SubScene#height", "Double=0.0");
85     ourFromSource.put("javafx.scene.SubScene#width", "Double=0.0");
86     ourFromSource.put("javafx.scene.chart.AreaChart#createSymbols", "Boolean=true");
87     ourFromSource.put("javafx.scene.chart.Axis#label", "String=");
88     ourFromSource.put("javafx.scene.chart.BarChart#barGap", "Double=4.0");
89     ourFromSource.put("javafx.scene.chart.BarChart#categoryGap", "Double=10.0");
90     ourFromSource.put("javafx.scene.chart.Chart#title", "String=");
91     ourFromSource.put("javafx.scene.chart.LineChart#axisSortingPolicy", "Enum=X_AXIS");
92     ourFromSource.put("javafx.scene.chart.LineChart#createSymbols", "Boolean=true");
93     ourFromSource.put("javafx.scene.chart.StackedAreaChart#createSymbols", "Boolean=true");
94     ourFromSource.put("javafx.scene.chart.StackedBarChart#categoryGap", "Double=10.0");
95     ourFromSource.put("javafx.scene.chart.XYChart#alternativeColumnFillVisible", "Boolean=false");
96     ourFromSource.put("javafx.scene.chart.XYChart#alternativeRowFillVisible", "Boolean=true");
97     ourFromSource.put("javafx.scene.chart.XYChart#horizontalGridLinesVisible", "Boolean=true");
98     ourFromSource.put("javafx.scene.chart.XYChart#horizontalZeroLineVisible", "Boolean=true");
99     ourFromSource.put("javafx.scene.chart.XYChart#verticalGridLinesVisible", "Boolean=true");
100     ourFromSource.put("javafx.scene.chart.XYChart#verticalZeroLineVisible", "Boolean=true");
101     ourFromSource.put("javafx.scene.control.ComboBoxBase#editable", "Boolean=false");
102     ourFromSource.put("javafx.scene.control.CustomMenuItem#hideOnClick", "Boolean=true");
103     ourFromSource.put("javafx.scene.control.Labeled#alignment", "Enum=CENTER_LEFT");
104     ourFromSource.put("javafx.scene.control.Labeled#mnemonicParsing", "Boolean=false");
105     ourFromSource.put("javafx.scene.control.SpinnerValueFactory#wrapAround", "Boolean=false");
106     ourFromSource.put("javafx.scene.control.TableSelectionModel#cellSelectionEnabled", "Boolean=false");
107     ourFromSource.put("javafx.scene.media.AudioClip#balance", "Double=0.0");
108     ourFromSource.put("javafx.scene.media.AudioClip#cycleCount", "Integer=1");
109     ourFromSource.put("javafx.scene.media.AudioClip#pan", "Double=0.0");
110     ourFromSource.put("javafx.scene.media.AudioClip#priority", "Integer=0");
111     ourFromSource.put("javafx.scene.media.AudioClip#rate", "Double=1.0");
112     ourFromSource.put("javafx.scene.media.AudioClip#volume", "Double=1.0");
113     ourFromSource.put("javafx.scene.media.AudioEqualizer#enabled", "Boolean=false");
114     ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumInterval", "Double=0.1");
115     ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumNumBands", "Integer=128");
116     ourFromSource.put("javafx.scene.media.MediaPlayer#audioSpectrumThreshold", "Integer=-60");
117     ourFromSource.put("javafx.scene.media.MediaPlayer#autoPlay", "Boolean=false");
118     ourFromSource.put("javafx.scene.media.MediaPlayer#balance", "Double=0.0");
119     ourFromSource.put("javafx.scene.media.MediaPlayer#cycleCount", "Integer=1");
120     ourFromSource.put("javafx.scene.media.MediaPlayer#mute", "Boolean=false");
121     ourFromSource.put("javafx.scene.media.MediaPlayer#rate", "Double=1.0");
122     ourFromSource.put("javafx.scene.media.MediaPlayer#volume", "Double=1.0");
123     ourFromSource.put("javafx.stage.PopupWindow#anchorLocation", "Enum=WINDOW_TOP_LEFT");
124     ourFromSource.put("javafx.stage.PopupWindow#autoHide", "Boolean=false");
125     ourFromSource.put("javafx.stage.PopupWindow#consumeAutoHidingEvents", "Boolean=true");
126   }
127
128   private static final Set<String> ourSkippedProperties = new HashSet<>(
129     Arrays.asList("javafx.scene.web.HTMLEditor#htmlText",
130                   "javafx.scene.web.WebEngine#userAgent",
131                   "javafx.scene.control.ButtonBar#buttonOrder"));
132
133   static class TypedValue {
134
135     private final String kind;
136     private final String value;
137
138     TypedValue(@NotNull String kind, @NotNull String value) {
139       this.kind = kind;
140       this.value = value;
141     }
142
143     public String getKind() {
144       return kind;
145     }
146
147     public String getValue() {
148       return value;
149     }
150
151     @Override
152     public boolean equals(Object o) {
153       if (this == o) return true;
154       if (!(o instanceof TypedValue)) return false;
155
156       TypedValue that = (TypedValue)o;
157       return kind.equals(that.kind) && value.equals(that.value);
158     }
159
160     @Override
161     public int hashCode() {
162       return kind.hashCode() ^ value.hashCode();
163     }
164
165     @Override
166     public String toString() {
167       return kind + ':' + value;
168     }
169   }
170
171   static class ContainedValue extends TypedValue {
172     private final String declaringClass;
173
174     public ContainedValue(@NotNull String kind, @NotNull String value, @NotNull String declaringClass) {
175       super(kind, value);
176       this.declaringClass = declaringClass;
177     }
178
179     public String getDeclaringClass() {
180       return declaringClass;
181     }
182   }
183
184   /**
185    * Attempt to instantiate JavaFX classes on the FX thread, obtain default property values from instantiated objects.
186    */
187   private static void generate() {
188     System.out.println("--- JavaFX default property values ---");
189
190     final Map<String, Map<String, ContainedValue>> containedProperties = new TreeMap<>();
191     final Map<String, Map<String, TypedValue>> declaredProperties = new TreeMap<>();
192     final Map<String, Map<String, Set<String>>> overriddenProperties = new TreeMap<>();
193     final Map<String, String> superClasses = new TreeMap<>();
194     try (final ZipInputStream zip = new ZipInputStream(new FileInputStream(new File(BINARIES_PATH)))) {
195       for (ZipEntry ze = zip.getNextEntry(); ze != null; ze = zip.getNextEntry()) {
196         final String entryName = ze.getName();
197         if (!ze.isDirectory() && entryName.endsWith(".class") && entryName.startsWith("javafx")) {
198           final String className = entryName.substring(0, entryName.lastIndexOf('.')).replace('/', '.');
199           Class<?> currentClass = Class.forName(className);
200           if (!Modifier.isPublic(currentClass.getModifiers()) && !Modifier.isProtected(currentClass.getModifiers())) continue;
201           if (currentClass.getName().startsWith("javafx.beans")) continue;
202           if (currentClass.getName().matches(".*\\$\\d+$")) continue;
203           BeanInfo info = Introspector.getBeanInfo(currentClass);
204           if (currentClass.getSuperclass() != null) {
205             superClasses.put(currentClass.getName(), currentClass.getSuperclass().getName());
206           }
207
208           Set<String> bindings = new TreeSet<>();
209           for (MethodDescriptor methodDesc : info.getMethodDescriptors()) {
210             if (methodDesc.getName().endsWith("Property") &&
211                 !methodDesc.getMethod().getGenericReturnType().toString().contains("ReadOnly")) {
212               bindings.add(methodDesc.getName());
213             }
214           }
215
216           Object obj = null;
217           for (PropertyDescriptor desc : info.getPropertyDescriptors()) {
218             final String propName = desc.getName();
219             final String propQualifiedName = currentClass.getName() + "#" + propName;
220             if (ourSkippedProperties.contains(propQualifiedName)) continue;
221             final Method setter = desc.getWriteMethod();
222             final boolean hasBinding = bindings.contains(propName + "Property");
223             if (setter == null || !hasBinding) continue;
224             final Type type = setter.getGenericParameterTypes()[0];
225
226             if (type instanceof Class) {
227               final Class<?> paramCls = (Class)type;
228               final String kind = kind(paramCls);
229               if (kind != null) {
230                 if (obj == null) {
231                   obj = instantiate(currentClass);
232                   if (obj == null) break;
233                 }
234                 final Object value;
235                 final Method getter = desc.getReadMethod();
236                 try {
237                   value = getter.invoke(obj);
238                 }
239                 catch (IllegalAccessException | InvocationTargetException e) {
240                   throw new RuntimeException("Can't invoke " + getter + " on " + currentClass, e);
241                 }
242                 if (value != null) {
243                   if (!value.equals(validate(paramCls, value))) {
244                     throw new RuntimeException("Invalid " + currentClass + "#" + propName + ":" + paramCls + "=" + value);
245                   }
246                   final Class<?> declaringClass = getter.getDeclaringClass();
247                   final ContainedValue newValue = new ContainedValue(kind, String.valueOf(value), declaringClass.getName());
248                   if (declaringClass.getName().startsWith("javafx")) {
249                     containedProperties
250                       .computeIfAbsent(currentClass.getName(), unused -> new TreeMap<>())
251                       .put(propName, newValue);
252                   }
253
254                   final Map<String, TypedValue> shareableProperties =
255                     declaredProperties.computeIfAbsent(declaringClass.getName(), unused -> new TreeMap<>());
256                   final TypedValue sharedValue = shareableProperties.get(propName);
257
258                   if (sharedValue == null) {
259                     shareableProperties.put(propName, newValue);
260                   }
261                   else if (!sharedValue.equals(newValue)) {
262                     final Set<String> multipleValues = overriddenProperties
263                       .computeIfAbsent(declaringClass.getName(), unused -> new TreeMap<>())
264                       .computeIfAbsent(propName, unused -> new TreeSet<>());
265                     multipleValues.add(sharedValue.getValue());
266                     multipleValues.add(newValue.getValue());
267                   }
268                 }
269               }
270             }
271           }
272         }
273       }
274     }
275     catch (IOException | ClassNotFoundException | IntrospectionException e) {
276       e.printStackTrace();
277     }
278
279     System.out.println("-------- Collected from sources ---------");
280     ourFromSource.forEach((k, v) -> System.out.println(k + ":" + v));
281
282     System.out.println("-------- Shared (not overridden) ---------");
283     declaredProperties.forEach(
284       (className, propertyMap) -> {
285         final Map<String, Set<String>> multipleValues = overriddenProperties.getOrDefault(className, Collections.emptyMap());
286         propertyMap.forEach((propName, typedValue) -> {
287           if (!multipleValues.containsKey(propName)) {
288             System.out.println(className + "#" + propName + ":" + typedValue.getKind() + "=" + typedValue.getValue());
289           }
290         });
291       });
292     System.out.println("-------- Overridden in subclass ---------");
293     final Map<String, Map<String, ContainedValue>> fromSource = new TreeMap<>();
294     ourFromSource.forEach((classAndPropName, kindAndValue) -> {
295       final int p1 = classAndPropName.indexOf('#');
296       if (p1 > 0 && p1 < classAndPropName.length()) {
297         final String className = classAndPropName.substring(0, p1);
298         final String propName = classAndPropName.substring(p1 + 1);
299         final int p3 = kindAndValue.indexOf('=');
300         if (p3 > 0 && p3 < kindAndValue.length()) {
301           final String kind = kindAndValue.substring(0, p3);
302           final String value = kindAndValue.substring(p3 + 1);
303           final Map<String, ContainedValue> propMap = fromSource.computeIfAbsent(className, unused -> new TreeMap<>());
304           if (!propMap.containsKey(propName)) {
305             final ContainedValue valueFromSource = new ContainedValue(kind, value, className);
306             propMap.put(propName, valueFromSource);
307           }
308         }
309       }
310     });
311     containedProperties.forEach(
312       (className, propertyMap) -> propertyMap.forEach(
313         (propName, propValue) -> {
314           final ContainedValue sourceValue = fromSource.getOrDefault(className, Collections.emptyMap()).get(propName);
315           if (sourceValue != null &&
316               sourceValue.equals(propValue)) {
317             return;
318           }
319           final Map<String, Set<String>> multipleValues = overriddenProperties.get(propValue.getDeclaringClass());
320           if (multipleValues != null && multipleValues.get(propName) != null) {
321             boolean sameValueInSuperClass = false;
322             for (String scName = superClasses.get(className); scName != null; scName = superClasses.get(scName)) {
323               final Map<String, ContainedValue> superPropMap = containedProperties.getOrDefault(scName, fromSource.get(scName));
324               if (superPropMap != null) {
325                 ContainedValue superValue = superPropMap.get(propName);
326                 if (superValue != null) {
327                   sameValueInSuperClass = superValue.equals(propValue);
328                   break;
329                 }
330               }
331             }
332             if (!sameValueInSuperClass) {
333               System.out.println(className + "#" + propName + ":" + propValue.getKind() + "=" + propValue.getValue());
334             }
335           }
336         }));
337
338     System.out.println("-------- Overridden properties' values ---------");
339     overriddenProperties
340       .forEach((className, propertyValues) -> propertyValues.entrySet()
341         .forEach(e -> System.out.println("-- " + className + "#" + e.getKey() + e.getValue())));
342     System.out.println("-------- Skipped properties ---------");
343     ourSkippedProperties.forEach(propName -> System.out.println("-- " + propName));
344   }
345
346   private static String kind(Class aCls) {
347     String kind = null;
348     if (aCls.isPrimitive()) {
349       kind = ourToBoxed.get(aCls).getSimpleName();
350     }
351     else if (aCls.isEnum()) {
352       kind = "Enum";
353     }
354     else if (Number.class.isAssignableFrom(aCls) || Boolean.class.isAssignableFrom(aCls) || Character.class.isAssignableFrom(aCls)) {
355       kind = aCls.getSimpleName();
356     }
357     else if (CharSequence.class.isAssignableFrom(aCls)) {
358       kind = "String";
359     }
360     return kind;
361   }
362
363   private static Object validate(Class<?> aCls, Object val) {
364     try {
365       if (aCls.isPrimitive()) {
366         aCls = ourToBoxed.get(aCls);
367         Method valueOf = aCls.getDeclaredMethod("valueOf", String.class);
368         return valueOf.invoke(null, String.valueOf(val));
369       }
370       if (Number.class.isAssignableFrom(aCls) || Boolean.class.isAssignableFrom(aCls) || Character.class.isAssignableFrom(aCls)) {
371         Method valueOf = aCls.getDeclaredMethod("valueOf", String.class);
372         return valueOf.invoke(null, String.valueOf(val));
373       }
374       if (CharSequence.class.isAssignableFrom(aCls)) {
375         return val;
376       }
377       if (aCls.isEnum()) {
378         Method valueOf = aCls.getDeclaredMethod("valueOf", String.class);
379         return valueOf.invoke(null, String.valueOf(val));
380       }
381       throw new IllegalStateException("Cannot cast " + val + " to unsupported class " + aCls);
382     }
383     catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
384       throw new IllegalStateException("Cannot cast " + val + " to " + aCls, e);
385     }
386   }
387
388   private static Object instantiate(Class<?> aClass) {
389     try {
390       if (Modifier.isAbstract(aClass.getModifiers())) return null;
391
392       final Constructor<?> constructor;
393       try {
394         constructor = aClass.getConstructor();
395       }
396       catch (NoSuchMethodException e) {
397         return null;
398       }
399       constructor.setAccessible(true);
400       return constructor.newInstance();
401     }
402     catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
403       System.err.println("Not instantiated " + aClass + " " + e);
404       return null;
405     }
406   }
407
408   private static final Set<String> ourSourceClasses = new TreeSet<>();
409
410   static {
411     ourSourceClasses.add("javafx.animation.Animation");
412     ourSourceClasses.add("javafx.concurrent.ScheduledService");
413     ourSourceClasses.add("javafx.print.JobSettings");
414     ourSourceClasses.add("javafx.scene.Camera");
415     ourSourceClasses.add("javafx.scene.LightBase");
416     ourSourceClasses.add("javafx.scene.Node");
417     ourSourceClasses.add("javafx.scene.Scene");
418     ourSourceClasses.add("javafx.scene.SubScene");
419     ourSourceClasses.add("javafx.scene.canvas.GraphicsContext");
420     ourSourceClasses.add("javafx.scene.chart.AreaChart");
421     ourSourceClasses.add("javafx.scene.chart.Axis");
422     ourSourceClasses.add("javafx.scene.chart.BarChart");
423     ourSourceClasses.add("javafx.scene.chart.Chart");
424     ourSourceClasses.add("javafx.scene.chart.LineChart");
425     ourSourceClasses.add("javafx.scene.chart.PieChart");
426     ourSourceClasses.add("javafx.scene.chart.StackedAreaChart");
427     ourSourceClasses.add("javafx.scene.chart.StackedBarChart");
428     ourSourceClasses.add("javafx.scene.chart.ValueAxis");
429     ourSourceClasses.add("javafx.scene.chart.XYChart");
430     ourSourceClasses.add("javafx.scene.control.Alert");
431     ourSourceClasses.add("javafx.scene.control.ComboBoxBase");
432     ourSourceClasses.add("javafx.scene.control.Labeled");
433     ourSourceClasses.add("javafx.scene.control.MultipleSelectionModel");
434     ourSourceClasses.add("javafx.scene.control.SpinnerValueFactory");
435     ourSourceClasses.add("javafx.scene.control.SpinnerValueFactory");
436     ourSourceClasses.add("javafx.scene.control.SpinnerValueFactory");
437     ourSourceClasses.add("javafx.scene.control.TableColumnBase");
438     ourSourceClasses.add("javafx.scene.control.TableSelectionModel");
439     ourSourceClasses.add("javafx.scene.control.TextFormatter");
440     ourSourceClasses.add("javafx.scene.control.TextInputControl");
441     ourSourceClasses.add("javafx.scene.control.Toggle");
442     ourSourceClasses.add("javafx.scene.input.DragEvent");
443     ourSourceClasses.add("javafx.scene.input.Dragboard");
444     ourSourceClasses.add("javafx.scene.input.MouseEvent");
445     ourSourceClasses.add("javafx.scene.media.AudioClip");
446     ourSourceClasses.add("javafx.scene.media.AudioEqualizer");
447     ourSourceClasses.add("javafx.scene.media.MediaPlayer");
448     ourSourceClasses.add("javafx.scene.shape.PathElement");
449     ourSourceClasses.add("javafx.scene.shape.Shape");
450     ourSourceClasses.add("javafx.scene.shape.Shape3D");
451     ourSourceClasses.add("javafx.scene.web.WebHistory");
452     ourSourceClasses.add("javafx.stage.PopupWindow");
453     ourSourceClasses.add("javafx.stage.Window");
454   }
455
456   private static void scanSource() {
457
458     Pattern defaultValueJavaDoc = Pattern.compile("^.*\\*\\s*@defaultValue\\s*(.+)\\s*$");
459     Pattern fieldDecl = Pattern.compile("^.*\\s(\\w+)\\s*(=.*|;)\\s*$");
460     Pattern methodDecl = Pattern.compile("^.*\\s(\\w+)\\s*\\(\\).*$");
461     Pattern propertyDecl = Pattern.compile("^.*Property\\S*\\s+(\\w+)\\s*=\\s*(.+)[;{].*$");
462
463     Map<String, String> props = new TreeMap<>();
464     try (final ZipInputStream zip = new ZipInputStream(new FileInputStream(new File(SOURCE_PATH)))) {
465       byte[] buffer = new byte[1 << 16];
466       for (ZipEntry ze = zip.getNextEntry(); ze != null; ze = zip.getNextEntry()) {
467         final String eName = ze.getName();
468         if (!ze.isDirectory() && eName.endsWith(".java") && eName.startsWith("javafx")) {
469           String className = eName.substring(0, eName.lastIndexOf('.')).replace('/', '.');
470           if (ourSourceClasses.contains(className)) {
471             StringBuilder text = new StringBuilder();
472             int len;
473             while ((len = zip.read(buffer)) > 0) {
474               String str = new String(buffer, 0, len);
475               text.append(str);
476             }
477             String[] lines = text.toString().split("\n");
478             int state = 0;
479             String name = null;
480             String value = null;
481             for (String s : lines) {
482               Matcher m;
483               m = propertyDecl.matcher(s);
484               if (m.matches()) {
485                 name = m.group(1);
486                 value = m.group(2);
487                 if (!"null".equals(value)) {
488                   props.put(className + "#" + name, value);
489                 }
490               }
491               switch (state) {
492                 case 0:
493                   m = defaultValueJavaDoc.matcher(s);
494                   if (m.matches()) {
495                     state = 1;
496                     value = m.group(1);
497                   }
498                   break;
499                 case 1:
500                   if (s.contains("*/")) state = 2;
501                   break;
502                 case 2:
503                   if (!s.trim().isEmpty()) {
504                     state = 0;
505                     m = fieldDecl.matcher(s);
506                     if (m.matches()) {
507                       name = m.group(1);
508                     }
509                     else {
510                       m = methodDecl.matcher(s);
511                       if (m.matches()) {
512                         name = m.group(1);
513                       }
514                     }
515                     if (name != null && value != null && !"null".equals(value)) {
516                       props.put(className + "#" + name, value);
517                     }
518                     name = value = null;
519                   }
520                   break;
521               }
522             }
523           }
524         }
525       }
526     }
527     catch (IOException e) {
528       System.err.println("Failed to read sources " + e);
529     }
530     System.out.println("--- Default values collected from JavaDoc and declarations. To be reviewed and manually edited ---");
531     props.forEach((n, v) -> System.out.println(n + "=" + v));
532   }
533 }