Okay, Yan, we might need your Java Kung Fu here!
While I’ve been working on a “Chess Clocks” class, I’ve finally put my finger on a bug I’d been “noticing out of the corner of my eye” but figured I was doing something wrong. But actually today I finally dug in and I think it’s a Vassal subtlety that impacts custom classes.
The routine PropertiesWindow.initialize() is experiencing (and improperly ignoring) ClassNotFoundException’s when culling child classes from the “originalState” of the class being edited – resulting in duplicate copies of entries for all child classes that experience the exception. The exception occurs, ironically, in the CANCEL path – in other words to reproduce the bug, you open a parent class’ properties, hit Cancel, and then save the module, and it results in duplication of any child class that is a custom class, because the routine that is supposed to “cull” the child class entries from the one being edited is experiencing and ignoring a ClassNotFoundException.
The following code (in VASSAL.configure.PropertiesWindow) is called whenever the properties for a class (e.g. “Map”) are opened in the Editor. The exception is being thrown when Class.forName gets called, if the class in question is a “custom class”. Somehow custom classes are not on the “classpath” for this method even though the module and editor themselves are able to load the custom classes. So the FIX would be figuring out how to get forName to include any loaded custom classes in its classpath. (OR the more brute force fix would be NOT to ignore the no-such-class and “cull the child entries anyway”).
protected void initialize(final Configurable target, HelpWindow helpWindow) {
this.target = target;
originalState = target.getBuildElement(Builder.createNewDocument());
Node child = originalState.getFirstChild();
while (child != null) {
Node nextChild = child.getNextSibling();
if (Node.ELEMENT_NODE == child.getNodeType()) {
// Cull Buildables from the state.
try {
final Class<?> c = Class.forName(((Element)child).getTagName());
if (Buildable.class.isAssignableFrom(c)) {
originalState.removeChild(child);
}
}
catch (ClassNotFoundException e) {
// This element doesn't correspond to a class. Skip it.
}
catch (ExceptionInInitializerError e) {
ErrorDialog.bug(e);
}
catch (LinkageError e) {
ErrorDialog.bug(e);
}
}
child = nextChild;
}
.
.
.
The exception is happening for all custom classes – somehow they are not on the “classpath” used by the Class.forName method used here.
REPRO Steps (100% repro)
- Edit any module that has custom classes which are “child classes” (commonly, of Map, e.g. PieceMover or KeyBufferer). An example module is For The People 3.2. Size of module doesn’t really matter this is a “simple” bug.
- Open the properties for the parent class (in this case Map) – double click on its entry.
- Hit the CANCEL button.
- Save the module.
- Now examine the module’s buildfile, looking for entries for the child custom class(es)
Expected: One entry for each custom class
Actual: Duplicate entries for each custom class (and for every time you open Map properties and cancel, you get another duplicate copy).
(Oh, and I did post a condensed version of this to the bug tracker – vassalengine.org/tracker/sho … i?id=13146)