...
Most of these are not too hard. The last one, however, needs a bit of further thought.
Using proxies to
...
Handling proxy data
...
detect changes for "subtransactions"
We could use a proxy to implement "subtransactions", the idea that the original objects are only changed when the subtransaction "commits". These original objects can then be saved by some other (real) transaction - or even another subtransaction. This allows for layered screens where for instance a Dialog can be used to edit some thingy without those changes becoming permanent even when the dialog is cancelled.
There are two ways to do this:
- The proxy itself keeps copies of all original values and returns those copies when getters and setters are called. The wrapped (original) object remains onchanged. Only when the context is committed will the data be copied to the original. This is the "pessimistic" approach: we assume that shit will happen and will not change anything until we're pretty sure.
- We can let the proxy make copies of all of the original data and then propagate all changes to the original immediately. Only when we rollback do we reset all originals by restoring the original data stored. This is the optimistic approach: what could possibly go wrong?
The latter one is undesirable for the following reasons:
- If something goes wrong you need to do the right thing in all circumstances (rolling back) or the compromised/wrong data gets propagated to the real model anyway. As developers are idiots this is a Bad Plan(c).
- Changing the original also means that other parts of the screen (which show data from the original) will change. This is confusing and performance-wise expensive.
So we get rule 1: data is kept in the proxy and only copied back to the original at special times
Handling proxy data
Key concept: intercept all getters and setters, and where needed replace the data gotten with different copies. We can use this to replace lists with ObservableLists and ManyToOne relation properties to a proxy of that parent.
...
The latter solves a lot of issues. We can now store the proxy data inside the real fields of the proxy, and this means that any method inside the proxy that uses those fields will now work and use the correct (replaced) data. This also saves us from the need to create separate storage for the fields in the One way to call toString() works so we have no choice but to delegate it to the original. But the original also has its original values (not the ones replaced) so toString renders old data.
What needs to be generated as a proxy
For a class Orig we need to generate a class OrigNNNN extends Orig. We should abort if Orig is final - or if it contains final members (other than the finals from Object).
The OrigNNNN class will have extra fields generated to keep data:
Field | Type | Description |
---|---|---|
__handler | IQProxyDataHandler | The data handler for the proxy which receives all delegated calls, and which contains a reference to the QDataContext and other related data. |
__original | Orig | The pointer to the original, wrapped instance. |
In addition the proxy contains all fields from Orig because it extends Orig. So the start of the class looks like this:
Code Block |
---|
public class Orig$sdads1212 extends Orig {
private IQProxyHandler __handler;
private Orig __original; |
Getters and setters
For each getter T getXxxx() we generate a new accessor method as follows:
Code Block |
---|
public T __getXxxx() {
return super.getXxxx();
}
static private Method __getXxxxM = (initialized to the above method) |
We do the same for all setters: void setXxxx(T value) generates:
Code Block |
---|
public void __setXxxx(T value) {
super.setXxxx(value);
}
static private final Method __setXxxxM = (initialized to the above method) |
These are generated in the proxy, and they are needed so that we can actually change the backing fields of the proxy when we need to.
For each of these getters and setters we will also generate static private fields containing Method references to these getters and setters: the __setXxxxM and __getXxxxM fields.
The new getter method delegates immediately to the handler:
Code Block |
---|
static private final __getXxxxO = (initialized to Orig.getXxxx);
public T getXxxx() {
return __handler.onGet(this, __original, __getXxxxO, __getXxxM);
} |
The values passed to the handler allow access to both the proxy and the original, and using the two method references you can get data from both the original (using __getXxxxO method reference) or from the proxy (using __getXxxxM, which uses the "redirected" getter accessing super.getXxxx()).
Something sneakily similar is generated for the getter:
Code Block |
---|
static private final __setXxxxO = (initialized to Orig.setXxxx)
public void setXxxx(T value) {
__handler.onSet(this, __original, __setXxxxO, __setXxxxM, value);
} |