2018-04-11

GitHub project: mikenakis-rumination

Making plain old java objects aware of their own mutations.


The mikenakis-rumination logo.
Based on original from free-illustrations.gatag.net
Used under CC BY License.


License


This creative work is explicitly published under No License. This means that I remain the exclusive copyright holder of this creative work, and you may not do anything with it other than view its source code and admire it. More information here: michael.gr - Open Source but No License.

If you would like to do anything more with this creative work, please contact me.

What is mikenakis-rumination? 


`mikenakis-rumination` is a java agent and associated class library for making plain old java objects aware of their own mutations.

Rumination is a term that I have coined to refer to the ability of an object to be aware of mutations on itself.

Rumination is important because it is the first step in making objects observable: Once an object is aware of changes to its own state, it can then offer means by which external observers can register to receive notifications about these changes. Rumination is not concerned with observability, it is only concerned with the narrow concept of self-mutation-awareness.

Rumination works by having some overridable method of the object invoked each time one of the setters of the object alters some piece of the state of the object. One can achieve this manually, by hand-coding an invocation to the overridable from within each setter, but that is tedious and error-prone. The idea behind rumination is to systematize this process, so that the the overridable gets invoked automagically, without us having to write any additional code.

The dictionary offers two meanings for the word ruminate:
  • One is "to chew the cud, as a ruminant", and it relates to the overridable being invoked to re-process a value which was received by a setter, so it is like eating one's own food once again. (And hence the goat in the logo.)
  • The other meaning is "to meditate or muse; ponder" which is also applicable, because the process is introspective in nature.
Rumination works by means of a java agent which gets invoked by the JVM to transform classes as they are being loaded. (Actually, it works as an AgentClaire Interceptor, which is essentially the same thing.)

A class is a ruminant if it is marked with the `@Ruminant` annotation, or if it is a descendant of a ruminant. This means that you only have to mark a certain base class as a ruminant, and then all of its descendants will automatically be ruminants without the need to add the `@Ruminant` annotation to each one of them.

The rumination processor examines ruminant classes for setters. A setter is a method which is of `void` return type, has a name starting with `set` and followed by the name of an instance field, accepts only one parameter which is of the same type as the instance field, and ends with a sequence of bytecode instructions that store the value of the parameter into the field before returning.

The rumination processor will replace those last bytecode instructions of a setter with bytecode instructions that do the following:
  1. Check whether the new value is same as the current value of the field; if so, return without doing anything.
  2. Store the new value into the field;
  3. Invoke the ruminator method, passing it the name of the field.
  4. return.
The ruminator method is of the following form:

    protected void onMemberChanged( String fieldName )
    {
        ...
    }

The `@Ruminant` annotation accepts an optional `String ruminatorMethodName()` parameter, whose default is `"onMemberChanged"`, so the name of the ruminator method can be changed.

The field is identified by name, because java does not (yet?) support field literals.

To see how rumination is used, check out the tests.

No comments:

Post a Comment