Files
mdg-filius/tools/java-oracle/src/GetterDefaults.java
T
julian.kiedaischandClaude Opus 5.5 8080e7dcb2 fix(fls): Gnutella's peer limit is random in Java, not a default
PeerToPeerAnwendung's constructor rolls its peer limit (3, 4 or 5). The
getter defaults measured it once each run — 4, then 5 — and c48d15d
committed 5 as a default. GetterDefaults now constructs every class 64
times and writes a value that varies as {"random": [...]}; the decoder
answers a reference to it as unresolved, and the topology layer reports
a peer limit the file does not set as null.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:12:14 +00:00

74 lines
3.9 KiB
Java

// 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 (`<void id="Integer0" property="iconTextGap"/>`) 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 <output json> <fqcn#property | fqcn::FIELD>...
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<String, Object> 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<String> 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());
}
}