Porting Java Inner and Anonymous Classes To TypeScript
Sometime between version 1.5 and 1.7.10 of Minecraft, the source code has undergone significant refactoring and made use of previously unused Java features, in particular, Nested and Anonymous classes, making my transpiler no longer "good enough" and thus needing to be improved.
Click here to jump straight to the solution and code, otherwise, if you're interested...
A Little History First
I am presently working on "Version 3" of my JavaScript Redstone Simulator.
"Version 1" was a clean room design where I looked at the logic of the game and then devised my own strategies to "copy" the behaviour of the game. While it was an quite an accomplishment that I managed to get something that worked, it suffered from a fundamental design problem. Each block in the 3D space was an independant instance of a "class". This worked fine for small schematics, of say 30x30x80, but when one made really big schematics, memory usage became a signifcant problem. If one tried opening a somewhat large schematic, let's say 256x256x128, 32-bit Chrome would allocate about 800MB, and then the tab would crash. Another issue was that getting the behaviour to be exactly like the game was near impossible.
With "Version 2" (the current live version), I decided to decompile the game's source code and try copy the logic as closely as possible. Like the game, my newer version used the Flyweight pattern which solved the memory problems of "Version 1" and as the behaviour was literally a port of the game's decompiled Java code into JavaScript, the behaviour was exact and the result was a perfect simulation of the game's behaviour in a web browser. The problem I found though was that porting the code was very time consuming, meaning that adding new block types was quite slow. To make matters worse, a new version of Minecraft had come out, making my simulator behaviour no longer up to date. It would also have been incredibly laborious trying to compare the source of the new and old version, finding changes, and then painstakingly porting them over to JavaScript.
So, for "Version 3" I decided that the only way to try keep up with the changes in the game in a timely manner, would be to automate the porting process as much as possible. Towards this end, I decided to wrtie a "good enough" transpiler which parses select parts of the game's decompiled Java code and emits equivalent TypeScript code. Then it would become merely a matter of writing integration between the game's ported logic and my simulator. I also landed up porting all the JavaScript for my interface to TypeScript.
A few days ago, after about 18 months of work, I finally experienced fruition of my labour, I got a simple schematic simulation working.
The version of the game I had been transpiling from was version 1.5, I hadn't updated in a long time because I wanted to avoid aiming for a moving target, but with it working now, before I did anymore work, I felt it would be best to move to the latest available decompiled source of the game, version 1.7.10.
The need to Transpile Java Anonymous and Nested Classes to TypeScript
As a C# guy who has done no Java development, nested classes were immediately recognizable, but Anonymous classes looked very weird, I didn't even know what they were called. Fortunately, a little searching on the web quickly enlightened me on their name and how they work.
Nested (or inner) classes are simple enough to understand and look like this:
class OuterClass {
...
class NestedClass {
...
}
}
Anonymous classes are a little more complicated:
class OuterClass {
public OuterClass.NestedClass nestedClassInstance = new OuterClass.NestedClass();
public OuterClass.NestedClass anonymousClassInstance = new OuterClass.NestedClass() {
public void SomeMethod() {
// Does something different from usual
}
};
public class NestedClass {
public void SomeMethod() {
// Does something
}
}
}
However, it's easy enough to understand that anonymous classes are inheriting from the nested class and overriding members.
So, now to devise a strategy of how best to represent these concepts in TypeScript. In JavaScript, this can definitely be achieved, by doing something like:
// A helper function allowing one to "Extend" classes:
var classExtender = function (_child, _parent) {
for (var p in _parent) {
if (_parent.hasOwnProperty(p)) {
_child[p] = _parent[p];
}
}
function __() {
this.constructor = _child;
}
__.prototype = _parent.prototype;
_child.prototype = new __();
};
var OuterClass = (function() {
function OuterClass() {
this.nestedClassInstance = new OuterClass.NestedClass();
// Anonymous class and it's instatiation:
this.anonymousClassInstance = new ((function(_super) {
classExtender(anonymousClass, _super);
function anonymousClass() {
_super.apply(this, arguments);
}
anonymousClass.prototype.SomeMethod = function() {
console.log("Does something different from usual");
};
return anonymousClass;
})(OuterClass.NestedClass))();
}
// Nested class definition
OuterClass.NestedClass = (function() {
function NestedClass() {
}
NestedClass.prototype.SomeMethod = function() {
console.log("Does something");
};
return NestedClass;
})();
return OuterClass;
})();
var outerClass = new OuterClass();
outerClass.nestedClassInstance.SomeMethod(); // Prints "Does something"
outerClass.anonymousClassInstance.SomeMethod(); // Prints "Does something different from usual"
I used the same patterns as TypeScript for class inheritance, if you want to understand better, check the Simple Inheritance example on the TypeScript Playground.
Okay, so it's possible, but ideally we want to be able to do it in a more readable TypeScript syntax. Some quick research shows that it's at least possible to nest classes. But I haven't found a way to do anonymous classes in plain TypeScript, which leaves us with code like this:
// A helper function allowing one to "Extend" classes:
var classExtender = function (_child, _parent) {
for (var p in _parent) {
if (_parent.hasOwnProperty(p)) {
_child[p] = _parent[p];
}
}
function __() {
this.constructor = _child;
}
__.prototype = _parent.prototype;
_child.prototype = new __();
};
class OuterClass {
public nestedClassInstance: OuterClass.NestedClass;
public anonymousClassInstance: OuterClass.NestedClass;
constructor() {
this.nestedClassInstance = new OuterClass.NestedClass();
this.anonymousClassInstance = new ((function(_super): { new(...args: any[]): OuterClass.NestedClass } {
classExtender(anonymousClass, _super);
function anonymousClass() {
_super(this, arguments);
}
anonymousClass.prototype.SomeMethod = function() {
console.log("Does something different from usual");
};
return <any>anonymousClass;
})(OuterClass.NestedClass))();
}
}
module OuterClass {
export class NestedClass {
public SomeMethod(): void {
console.log("Does something");
}
}
}
var outerClass = new OuterClass();
outerClass.nestedClassInstance.SomeMethod(); // Prints "Does something"
outerClass.anonymousClassInstance.SomeMethod(); // Prints "Does something different from usual"
The above works, the fact that we can do Nested Classes quite neatly in TypeScript clearly helps, but unfortunately I can't think of a better way to handle the anonymous classes. I'll probably make it a little neater by moving the extender into a separate source file, or possibly use the one that TypeScript generates.
So now that I have a pattern I can generate in TypeScript, it's now just a matter of implementing it in the transpiler.