// Measures the value a JavaBean getter returns on a freshly constructed object. // XMLEncoder shares equal Integer and Boolean objects: an int written once as // the result of a getter (``) is // later referenced by id — e.g. as a node's x coordinate. Decoding such a file // needs these defaults; they are measured here, not guessed. // // The same holds for static constants: the first Integer 0 in a file may be // written as `SwingConstants.CENTER`, and every later 0 refers to it. // // Usage: java -cp filius.jar:lib/*:classes GetterDefaults ... import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.FileOutputStream; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.TreeMap; public class GetterDefaults { public static void main(String[] args) throws Exception { Map result = new TreeMap<>(); for (int index = 1; index < args.length; index++) { String key = args[index]; if (key.contains("::")) { String className = key.substring(0, key.indexOf("::")); String field = key.substring(key.indexOf("::") + 2); String simpleName = className.substring(className.lastIndexOf('.') + 1); result.put(simpleName + "." + field, Class.forName(className).getField(field).get(null)); continue; } String className = key.substring(0, key.indexOf('#')); String property = key.substring(key.indexOf('#') + 1); String simpleName = className.substring(className.lastIndexOf('.') + 1); try { // Constructed many times: a constructor may roll its value (Gnutella's // peer limit is 3, 4 or 5 at random). Such a value is no default and is // written as {"random": [the values seen]}. java.util.TreeSet seen = new java.util.TreeSet<>(); Object value = null; for (int attempt = 0; attempt < 64; attempt++) { value = className.equals("java.lang.ThreadGroup") // A Thread's uncaughtExceptionHandler is its group; XMLDecoder runs // in the main thread, so that is the group asked. ? read(Thread.currentThread().getThreadGroup(), property) : read(Class.forName(className).getDeclaredConstructor().newInstance(), property); seen.add(String.valueOf(value)); } result.put(simpleName + "#" + property, seen.size() == 1 ? value : Map.of("random", new java.util.ArrayList<>(seen))); } catch (Throwable error) { Throwable cause = error.getCause() != null ? error.getCause() : error; result.put(simpleName + "#" + property, Map.of("error", cause.toString())); } } try (FileOutputStream out = new FileOutputStream(args[0])) { out.write((FlsOracle.Json.write(result, "") + "\n").getBytes(StandardCharsets.UTF_8)); } System.out.println("getter defaults: " + result.size()); System.exit(0); } static Object read(Object target, String property) throws Exception { Method getter = null; for (PropertyDescriptor descriptor : Introspector.getBeanInfo(target.getClass()).getPropertyDescriptors()) { if (descriptor.getName().equals(property)) getter = descriptor.getReadMethod(); } if (getter == null) getter = target.getClass().getMethod(property); Object value = getter.invoke(target); if (value == null || value instanceof Number || value instanceof Boolean || value instanceof String) return value; return Map.of("class", value.getClass().getName()); } }