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