package org.visage.runtime; /** * This class tests dependents maintainence for VisageBase instances. */ public class DependentsTest extends VisageTestCase { public void testAddRemove() { // create an VisageBase with 2 fields. VisageBase src = new VisageBase() { @Override public int count$() { return 2; } }; // fresh object, no dependents yet assertEquals(0, src.getListenerCount$()); // create two dependent objects VisageBase dep1 = new VisageBase(); VisageBase dep2 = new VisageBase(); // check dependent addition src.addDependent$(0, dep1, 0); assertEquals(1, src.getListenerCount$()); src.addDependent$(1, dep1, 1); assertEquals(2, src.getListenerCount$()); src.addDependent$(0, dep2, 0); assertEquals(3, src.getListenerCount$()); src.addDependent$(1, dep2, 1); assertEquals(4, src.getListenerCount$()); // check removals src.removeDependent$(0, dep1); assertEquals(3, src.getListenerCount$()); src.removeDependent$(1, dep1); assertEquals(2, src.getListenerCount$()); src.removeDependent$(1, dep2); assertEquals(1, src.getListenerCount$()); src.removeDependent$(0, dep2); assertEquals(0, src.getListenerCount$()); } public void testUpdate() { // create an object with two variables final VisageBase src = new VisageBase() { @Override public int count$() { return 2; } }; // check that update$ method is called as expected final int[] numTimesDep1Updated = new int[1]; // create two dependents and register for different 'depNum's. final VisageBase dep1 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDep1Updated[0]++; assertSame(src, srcObj); assertEquals(0, depNum); return true; } }; src.addDependent$(0, dep1, 0); final int[] numTimesDep2Updated = new int[1]; final VisageBase dep2 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDep2Updated[0]++; assertSame(src, srcObj); assertEquals(1, depNum); return true; } }; src.addDependent$(1, dep2, 1); // update zeroth var src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); // dep1's update$ should have been called // dep2's update$ should not have been called assertEquals(1, numTimesDep1Updated[0]); assertEquals(0, numTimesDep2Updated[0]); // update first var src.notifyDependents$(1, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); // dep1's update$ should not have been called // dep2's update$ should have been called assertEquals(1, numTimesDep1Updated[0]); assertEquals(1, numTimesDep2Updated[0]); } public void testAddDuringNotification() { // create an object with two variables final VisageBase src = new VisageBase() { @Override public int count$() { return 2; } }; // check that update$ method is called as expected final int[] numTimesDep1Updated = new int[1]; // create two dependents and register for different 'depNum's. final VisageBase dep1 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDep1Updated[0]++; assertSame(src, srcObj); assertEquals(0, depNum); return true; } }; final int[] numTimesDep2Updated = new int[1]; final VisageBase dep2 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { srcObj.addDependent$(0, dep1, 0); numTimesDep2Updated[0]++; assertSame(src, srcObj); assertEquals(1, depNum); return true; } }; src.addDependent$(1, dep2, 1); src.notifyDependents$(1, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, numTimesDep1Updated[0]); assertEquals(1, numTimesDep2Updated[0]); // dep2's update adds dep1 as dependent src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(1, numTimesDep1Updated[0]); assertEquals(1, numTimesDep2Updated[0]); } public void testRemoveDuringNotification() { // create an object with two variables final VisageBase src = new VisageBase() { @Override public int count$() { return 2; } }; // check that update$ method is called as expected final int[] numTimesDep1Updated = new int[1]; // create two dependents and register for different 'depNum's. final VisageBase dep1 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDep1Updated[0]++; assertSame(src, srcObj); assertEquals(0, depNum); return true; } }; final int[] numTimesDep2Updated = new int[1]; final VisageBase dep2 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { srcObj.removeDependent$(0, dep1); numTimesDep2Updated[0]++; assertSame(src, srcObj); assertEquals(1, depNum); return true; } }; src.addDependent$(0, dep1, 0); src.addDependent$(1, dep2, 1); src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); src.notifyDependents$(1, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(1, numTimesDep1Updated[0]); assertEquals(1, numTimesDep2Updated[0]); // dep2's update removed dep1 as dependent // so, we should not get dep1.update$ call src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); src.notifyDependents$(1, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(1, numTimesDep1Updated[0]); assertEquals(2, numTimesDep2Updated[0]); } public void testRemoveCurrentDuringNotification() { // create an object with two variables final VisageBase src = new VisageBase() { @Override public int count$() { return 2; } }; final int[] numTimesDep1Updated = new int[1]; // create two dependents and register for different 'depNum's. final VisageBase dep1 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDep1Updated[0]++; assertSame(src, srcObj); assertEquals(0, depNum); srcObj.removeDependent$(0, this); return true; } }; final int[] numTimesDep2Updated = new int[1]; // create two dependents and register for different 'depNum's. final VisageBase dep2 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDep2Updated[0]++; assertSame(src, srcObj); return true; } }; src.addDependent$(0, dep2, 0); src.addDependent$(0, dep1, 0); src.addDependent$(1, dep2, 1); assertEquals(3, src.getListenerCount$()); src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(1, numTimesDep1Updated[0]); assertEquals(1, numTimesDep2Updated[0]); // one dependent removed from notification loop assertEquals(2, src.getListenerCount$()); } public void testRemoveAllDuringNotification() { // create an object with one variable final VisageBase src = new VisageBase() { @Override public int count$() { return 1; } }; final int[] deleter = new int[1]; final VisageBase[] dependents = new VisageBase[3]; final VisageBase dep0 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { if (deleter[0] == 0) { for (VisageBase visage : dependents) { srcObj.removeDependent$(0, visage); } } return true; } }; final VisageBase dep1 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { if (deleter[0] == 1) { for (VisageBase visage : dependents) { srcObj.removeDependent$(0, visage); } } return true; } }; final VisageBase dep2 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { if (deleter[0] == 2) { for (VisageBase visage : dependents) { srcObj.removeDependent$(0, visage); } } return true; } }; dependents[0] = dep0; dependents[1] = dep1; dependents[2] = dep2; src.addDependent$(0, dep0, 0); src.addDependent$(0, dep1, 0); src.addDependent$(0, dep2, 0); assertEquals(3, src.getListenerCount$()); // remove all from the first inserted dependent deleter[0] = 0; src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src.getListenerCount$()); src.addDependent$(0, dep0, 0); src.addDependent$(0, dep1, 0); src.addDependent$(0, dep2, 0); assertEquals(3, src.getListenerCount$()); // remove all from the second (middle) inserted dependent deleter[0] = 1; src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src.getListenerCount$()); src.addDependent$(0, dep0, 0); src.addDependent$(0, dep1, 0); src.addDependent$(0, dep2, 0); assertEquals(3, src.getListenerCount$()); // removal all from the last inserted dependent deleter[0] = 2; src.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src.getListenerCount$()); } public void testSwitchDependence() { // create an object with one variable final VisageBase src1 = new VisageBase() { @Override public int count$() { return 1; } }; // create an object with one variable final VisageBase src2 = new VisageBase() { @Override public int count$() { return 1; } }; // check that update$ method is called as expected final int[] numTimesDepUpdated = new int[1]; final VisageBase dep = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { numTimesDepUpdated[0]++; assertEquals(0, depNum); return true; } }; // no listeners for both sources assertEquals(0, src1.getListenerCount$()); assertEquals(0, src2.getListenerCount$()); // add one listener for "src1" src1.addDependent$(0, dep, 0); assertEquals(1, src1.getListenerCount$()); src1.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(1, numTimesDepUpdated[0]); // switch the dependence of "dep" from "src1" to "src2" dep.switchDependence$(src1, 0, src2, 0, 0); assertEquals(0, src1.getListenerCount$()); assertEquals(1, src2.getListenerCount$()); src2.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(2, numTimesDepUpdated[0]); } public void testSwitchCurrentDuringNotification() { final VisageBase src1 = new VisageBase() { @Override public int count$() { return 1; } }; final VisageBase src2 = new VisageBase() { @Override public int count$() { return 1; } }; final VisageBase dep = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { // switch dependence of current object this.switchDependence$(src1, 0, src2, 0, 0); return true; } }; src1.addDependent$(0, dep, 0); assertEquals(1, src1.getListenerCount$()); assertEquals(0, src2.getListenerCount$()); src1.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src1.getListenerCount$()); assertEquals(1, src2.getListenerCount$()); } public void testSwitchAllDuringNotification() { final VisageBase src1 = new VisageBase() { @Override public int count$() { return 1; } }; final VisageBase src2 = new VisageBase() { @Override public int count$() { return 1; } }; final int[] switcher = new int[1]; final VisageBase[] dependents = new VisageBase[3]; final VisageBase dep0 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { if (switcher[0] == 0) { for (VisageBase visage : dependents) { visage.switchDependence$(src1, 0, src2, 0, 0); } } return true; } }; final VisageBase dep1 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { if (switcher[0] == 1) { for (VisageBase visage : dependents) { visage.switchDependence$(src1, 0, src2, 0, 0); } } return true; } }; final VisageBase dep2 = new VisageBase() { @Override public boolean update$(VisageObject srcObj, int depNum, int startPos, int endPos, int newLength, int phase) { if (switcher[0] == 2) { for (VisageBase visage : dependents) { visage.switchDependence$(src1, 0, src2, 0, 0); } } return true; } }; assertEquals(0, src1.getListenerCount$()); assertEquals(0, src2.getListenerCount$()); dependents[0] = dep0; dependents[1] = dep1; dependents[2] = dep2; src1.addDependent$(0, dep0, 0); src1.addDependent$(0, dep1, 0); src1.addDependent$(0, dep2, 0); assertEquals(3, src1.getListenerCount$()); // switch all from the first inserted dependent switcher[0] = 0; src1.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src1.getListenerCount$()); assertEquals(3, src2.getListenerCount$()); for (VisageObject d : dependents) { src2.removeDependent$(0, d); } src1.addDependent$(0, dep0, 0); src1.addDependent$(0, dep1, 0); src1.addDependent$(0, dep2, 0); assertEquals(3, src1.getListenerCount$()); assertEquals(0, src2.getListenerCount$()); // switch all from the second (middle) inserted dependent switcher[0] = 1; src1.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src1.getListenerCount$()); assertEquals(3, src2.getListenerCount$()); for (VisageObject d : dependents) { src2.removeDependent$(0, d); } src1.addDependent$(0, dep0, 0); src1.addDependent$(0, dep1, 0); src1.addDependent$(0, dep2, 0); assertEquals(3, src1.getListenerCount$()); // switch all from the last inserted dependent switcher[0] = 2; src1.notifyDependents$(0, VisageObject.PHASE_TRANS$CASCADE_INVALIDATE); assertEquals(0, src1.getListenerCount$()); assertEquals(3, src2.getListenerCount$()); } public void testFakeRemove() { // create an VisageBase with 2 fields. VisageBase src = new VisageBase() { @Override public int count$() { return 2; } }; VisageBase dep = new VisageBase(); src.addDependent$(0, dep, 0); src.addDependent$(1, dep, 1); assertEquals(2, src.getListenerCount$()); // remove something that was *not* added as dependent VisageObject fakeDep = new VisageBase(); src.removeDependent$(0, fakeDep); src.removeDependent$(1, fakeDep); // try invalid varNum too! src.removeDependent$(5, fakeDep); // still 2 dependents -- but made sure fake removals are handled ok assertEquals(2, src.getListenerCount$()); } }