Friday, August 14, 2015

JavaScript private data using createPrivateThis()

In JavaScript, all object properties are public.

But, coming from Java and a long line of languages before it, I've seen how private properties can enforce better designs.  Is there a convenient way to enforce private properties in JavaScript?

Up to now, there were four proposed solutions:
  1. Imaginary privacy: Put an underscore (_) in front of all private properties to indicate that they are private.
  2. Everything in the constructor: Use closure and attach all your functions to the this object; don't use Object.prototype.func = anymore.
  3. Weakmaps: http://www.nczonline.net/blog/2014/01/21/private-instance-members-with-weakmaps-in-javascript/
  4. Everything public: Why care?
I am now proposing a fifth solution: createPrivateThis().

My objectives were simple:
  1. Provide Java-style public and private data in JavaScript.
  2. Prevent read access to the private data from outside the object.
  3. Allow functions to be created on the prototype object as normal.
  4. Allow functions on the prototype object to access public and private data using the this pointer.
Here's the code that I came up with:
/**
 * Wrap the prototype functions and put the wrappers on the
 * object itself.  A wrapper merges the object and private
 * properties before the call, makes the call then unmerges
 * the object and private properties.
 *
 * Normally, you create your private data in the constructor
 * function and give distinct names to public and private
 * data.  Then, during function calls, you use the this
 * pointer for BOTH public and private data.  In ambiguous
 * situations, the private data is operated on with the
 * exception that adding/deleting data defaults to the
 * (public) object.
 *
 * It is possible to create private functions and variables
 * with the same name using the transitory getPrivateThis()
 * method but this is NOT recommended and is an advanced use.
 *
 * @param {Object} obj The object that needs private data.
 * @return {Object} The private data pointer (this_p).
 */
function createPrivateThis(obj) {
  // use Underscore.js or our own mini version
  var __ = (typeof _ === 'undefined')? {
    isFunction: function(f) {
        return f 
           & {}.toString.call(f) == '[object Function]';
    },
    has: function(o, k) {
      return o.hasOwnProperty(k);
    },
    keys: function(o) {
      return Object.keys(o);
    },
    clone: function(o) {
      var k, o2 = {};
      for (k in o) {
        if (o.hasOwnProperty(k)) {
          o2[k] = o[k];
        }
      }
      return o2;
    },
    each: function(oa, f) {
      var i, k;
      if (oa.constructor === Array) {
        for (i=0; i < oa.length; ++i) {
          f(oa[i]);
        }
      } else {
        for (k in oa) {
          if (oa.hasOwnProperty(k)) {
            f(oa[k], k);
          }
        }
      }
    },
    extend: function() {
      var a, arg, d = arguments[0];
      for (a=1; a < arguments.length; ++a) {
        arg = arguments[a];
        for (var k in arg) {
          if (arg.hasOwnProperty(k)) {
            d[k] = arg[k];
          }
        }
      }
      return d;
    },
    union: function() {
      var a = Array.prototype.concat.apply({}, arguments)
      var b = [], a1, i;
      while (a.length > 0) {
        a1 = a.pop();
        for (i=0; (i < b.length) && (a1 !== b[i]); ++i) ;
        if (i === b.length) {
          b.push(a1);
        }
      }
      return b;
    }
  }: _;
  var priv = {};
  var getPriv = function() {
    return priv;
  };
  // iterate over each function in the object prototype
  __.each(obj.constructor.prototype, function(value, key) {
    if (__.isFunction(value) && !__.has(obj, key)) {
      // intercept the function on the object itself
      obj[key] = function() {
        var pre = __.clone(priv);
        // combine the object and its private properties
        var full = __.extend({}, obj, priv);
        // add the getPrivateThis() access function
        full.getPrivateThis = getPriv;
        // invoke original function with combined object
        var r = value.apply(full, arguments);
        // remove the getPrivateThis() access function
        delete full.getPrivateThis;
        var objKeysToUpdate = {}, objKeysToDelete = {};
        var privKeysToUpdate = {}, privKeysToDelete = {};
        var allKeys = __.union(__.keys(pre), __.keys(priv),
          __.keys(obj), __.keys(full));
        // decide what to do with every key that we ever saw
        __.each(allKeys, function(key) {
          if (__.has(pre, key) !== __.has(priv, key)) {
            // private property was added or deleted
            // using getPrivateThis()
            if (__.has(full, key)) {
              // add/modify object property with the same name
              objKeysToUpdate[key] = full[key];
            } else if (!__.has(full, key) && __.has(obj, key)) {
              // delete object property with the same name
              objKeysToDelete[key] = 'delete';
            }
          } else if (__.has(full, key) && __.has(priv, key)) {
            // modify private property
            privKeysToUpdate[key] = full[key];
          } else if (!__.has(full, key) && __.has(priv, key)) {
            // delete private property
            privKeysToDelete[key] = 'delete';
          } else if (__.has(full, key) && !__.has(priv, key)) {
            // add/modify object property
            objKeysToUpdate[key] = full[key];
          } else if (!__.has(full, key) && __.has(obj, key)) {
            // delete object property
            objKeysToDelete[key] = 'delete';
          }
        });
        // do what we decided to do with the keys
        __.each(objKeysToUpdate, function(value, key) {
          obj[key] = value;
        });
        __.each(objKeysToDelete, function(value, key) {
          delete obj[key];
        });
        __.each(privKeysToUpdate, function(value, key) {
          priv[key] = value;
        });
        __.each(privKeysToDelete, function(value, key) {
          delete priv[key];
        });
        return r;
      };
    }
  });
  return priv;
}

The core idea is to create wrapper functions on the this object which automatically intercept the calls to the functions defined on the prototype object.  The automatically generated interception functions combine the this and the private data, make a call to the prototype function, then uncombine the data back into the this and the private data.

In practice, createPrivateThis() is used like this:
function Animal() {
  var this_p = createPrivateThis(this);
  this_p.nickname = "NoName";
  this.type = 'animal';
}

Animal.prototype.getPrivateNickname = function() {
  // this.nickname is private but we can access it inside here
  return this.nickname;
};

Animal.prototype.setPrivateNickname = function(nick) {
  // this.nickname is private but we can access it inside here
  this.nickname = nick;
};

Animal.prototype.getPublicType = function() {
  return this.type;
};

Animal.prototype.setPublicType = function(type) {
  this.type = type;
};

The private property, nickname, is not accessible outside the object.  It is accessed via the this pointer but it does not actually reside on the object.  The private property actually exists as a local variable in the createPrivateThis() function that is accessed via closure (only available inside the function).

Thursday, August 13, 2015

Make Mac Minecraft work on Oracle Java

My son likes to play Minecraft.  I like to use Oracle Java 8 instead of the decrepit Apple Java (6) that Apple insists on.  Can't Minecraft use Oracle Java 8?

It can but it took me 6 months to figure out how to do it right.  You can modify the Minecraft Mac application so it will work on whatever is installed, either Oracle Java 8 or Apple Java 6.  You can even do it without starting a Terminal (but I'll tell you how to do it in Terminal, too).

On MacOS X Yosemite:

1.  Make a copy of your Minecraft application for backup.

2.  Start Safari and go to this page:

https://raw.githubusercontent.com/tofi86/universalJavaApplicationStub/master/src/universalJavaApplicationStub

This is a bash script from https://github.com/tofi86/universalJavaApplicationStub GitHub project.

3.  Choose the "File" menu, then select the "Save As..." menu item.  Select the "Page Source" item in the "Format" dropdown list.  Now, save it using the default name (i.e. universalJavaApplicationStub) on your Desktop.

4.  Go to the Applications folder, right click on the Minecraft application and select "Show Package Contents".  A Finder window will open.  Double-click on the Contents folder to open it.  Start Finder and choose the "Go" menu, then select the "Go to Folder..." menu item.  Type "/Applications/Minecraft.app/Contents" and press the "Go" button.  A Finder window will open.

5.  Double-click on the MacOS folder.  If it is a Java application, you will probably see a single file named JavaApplicationStub in the MacOS folder.

6.  Drag the universalJavaApplicationStub into the MacOS folder.  Now, there are two files in there.

7.  Right click on universalJavaApplicationStub and select the "Get Info" menu item.  Open the "Name & Extensions" item.

8.  If the "Hide extension" checkbox is enabled and checked, uncheck it.

9.  In the text box, delete the extension (probably ".txt") and close the "Get Info" box.

10.  If you get "Are you sure you want to remove the extension ".txt"?" prompt, press the "Remove" button.  (The icon for universalJavaApplicationStub will change to a green CRT terminal icon with the text, "exec", on it.)  Leave the "MacOS" Finder window open.

11.  Oh, I lied.  You do have to use Terminal.  Open the Applications folder, open the Utilities and run Terminal application.

12.  Paste the following into Terminal and press the "Return" key to execute it:

chmod ugo+x /Applications/Minecraft.app/Contents/MacOS/universalJavaApplicationStub

This command adds eXecute permissions to the file for User, Group and Other.

If you don't get an error, it probably worked.  If you get an error, you can give up on this process and, don't worry, the Minecraft application is undamaged.  In either case, close the Terminal.

13.  Press the back button on the original Finder window to return to the Contents folder.

14.  Right click on the Info.plist file.  Select the "Open With" menu, then choose the "Other..." menu item.  Scroll down and select the "TextEdit" application.  The TextEdit application should start.

15.  Edit and save the file:

A.  Insert "universal" in front of "JavaApplicationStub" so it reads <string>universal JavaApplicationStub</string>.

B.  Insert an "X" at <key>Java</key> to make <key>JavaX</key>.

16.  Go to the Applications folder and run Minecraft.  It should work.  You're done.

If you want to perform this same process entirely in Terminal, start a Terminal and do this:

$ cp -r /Applications/Minecraft.app /Applications/Minecraft\ copy.app
$ cd /Applications/Minecraft.app/Contents
$ curl "https://raw.githubusercontent.com/tofi86/universalJavaApplicationStub/master/src/universalJavaApplicationStub" -o "MacOS/universalJavaApplicationStub"
$ chmod ugo+x MacOS/universalJavaApplicationStub
$ vi Info.plist
Press i to insert text
- <string>JavaApplicationStub</string>
+ <string>universalJavaApplicationStub</string>
- <key>Java</key>
+ <key>JavaX</key>
Press Esc, then type ZZ which will save

This technique should work for any Mac Java application, as long as you go to the correct .app folder.

Don't forget that you have to a backslash (\) before any spaces in the name when using Terminal so Minecraft copy.app becomes Minecraft\ copy.app.

If you need to troubleshoot, try running universalJavaApplicationStub from the Terminal and see if it launches Minecraft or gives you error messages.  If that works, try running the open command so open /Applications/Minecraft.app and see if that launches Minecraft or gives you error messages.  The error messages can be somewhat cryptic but, with Google, perhaps you can figure out what the issue is.

References:
http://apple.stackexchange.com/questions/88110/make-minecraft-or-java-preferences-app-run-on-java-7
http://gaming.stackexchange.com/questions/178178/making-minecraft-run-with-java-8-on-os-x-10-10
http://www.cgwerks.com/make-minecraft-work-mac-osx-yosemite-latest-java-8/
https://gist.github.com/pudquick/7518753
http://www.minecraftforum.net/forums/support/unmodified-minecraft-client/1858141-minecraft-x64-for-mac-with-java-7
https://bugs.mojang.com/browse/MCL-1049
http://mosx.tumblr.com/post/64402950499/os-x-tip-execute-java-apps-like-minecraft-or
http://stackoverflow.com/questions/14806709/application-is-using-java-6-from-apple-instead-of-java-7-from-oracle-on-mac-os-x
http://superuser.com/questions/490425/how-do-i-switch-between-java-7-and-java-6-on-os-x-10-8-2