You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/06/06 11:33:01 UTC

[1/2] git commit: ISIS-422: extending IsisJdoSupport to provide acccess to JDO PersistenceManager.

Updated Branches:
  refs/heads/master bd39f6806 -> c375a4f1d


ISIS-422: extending IsisJdoSupport to provide acccess to JDO PersistenceManager.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/53722484
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/53722484
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/53722484

Branch: refs/heads/master
Commit: 5372248448abc112bf0711667f887ccb2bfd7b59
Parents: bd39f68
Author: Dan Haywood <da...@apache.org>
Authored: Thu Jun 6 09:44:05 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Thu Jun 6 09:44:05 2013 +0100

----------------------------------------------------------------------
 .../jdo/applib/service/support/IsisJdoSupport.java |    4 ++++
 .../service/support/IsisJdoSupportImpl.java        |   13 ++++++++++++-
 2 files changed, 16 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/53722484/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/support/IsisJdoSupport.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/support/IsisJdoSupport.java b/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/support/IsisJdoSupport.java
index b081f35..9cc54df 100644
--- a/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/support/IsisJdoSupport.java
+++ b/component/objectstore/jdo/jdo-applib/src/main/java/org/apache/isis/objectstore/jdo/applib/service/support/IsisJdoSupport.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.objectstore.jdo.applib.service.support;
 
+import javax.jdo.PersistenceManager;
+
 import org.apache.isis.applib.annotation.Programmatic;
 
 /**
@@ -43,4 +45,6 @@ public interface IsisJdoSupport {
      */
     @Programmatic
     <T> T refresh(T domainObject);
+    
+    PersistenceManager getJdoPersistenceManager();
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/53722484/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/support/IsisJdoSupportImpl.java
----------------------------------------------------------------------
diff --git a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/support/IsisJdoSupportImpl.java b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/support/IsisJdoSupportImpl.java
index 647bd4e..d2a0a91 100644
--- a/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/support/IsisJdoSupportImpl.java
+++ b/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/service/support/IsisJdoSupportImpl.java
@@ -19,6 +19,8 @@
 
 package org.apache.isis.objectstore.jdo.datanucleus.service.support;
 
+import javax.jdo.PersistenceManager;
+
 import org.apache.isis.applib.annotation.Hidden;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
@@ -33,12 +35,16 @@ public class IsisJdoSupportImpl implements IsisJdoSupport {
     
     @Override
     public <T> T refresh(T domainObject) {
-        DataNucleusObjectStore objectStore = (DataNucleusObjectStore) getPersistenceSession().getObjectStore();
+        DataNucleusObjectStore objectStore = getObjectStore();
         ObjectAdapter adapter = getAdapterManager().adapterFor(domainObject);
         objectStore.refreshRoot(adapter);
         return domainObject;
     }
 
+    protected DataNucleusObjectStore getObjectStore() {
+        return (DataNucleusObjectStore) getPersistenceSession().getObjectStore();
+    }
+
     protected AdapterManager getAdapterManager() {
         return getPersistenceSession().getAdapterManager();
     }
@@ -51,4 +57,9 @@ public class IsisJdoSupportImpl implements IsisJdoSupport {
         return getPersistenceSession().getServicesInjector();
     }
 
+    @Override
+    public PersistenceManager getJdoPersistenceManager() {
+        return getObjectStore().getPersistenceManager();
+    }
+
 }


[2/2] git commit: ISIS-423: utility to automatically test bidir relationships.

Posted by da...@apache.org.
ISIS-423: utility to automatically test bidir relationships.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/c375a4f1
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/c375a4f1
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/c375a4f1

Branch: refs/heads/master
Commit: c375a4f1db84113b4d089e038389b97e2ae6803e
Parents: 5372248
Author: Dan Haywood <da...@apache.org>
Authored: Thu Jun 6 09:44:31 2013 +0100
Committer: Dan Haywood <da...@apache.org>
Committed: Thu Jun 6 09:44:31 2013 +0100

----------------------------------------------------------------------
 core/unittestsupport/pom.xml                       |   12 +
 ...irectionalRelationshipContractTestAbstract.java |  789 +++++++++++++++
 .../isis/core/unittestsupport/bidir/Child.java     |   52 +
 .../core/unittestsupport/bidir/CollectUtils.java   |   29 +
 .../core/unittestsupport/bidir/IndentPrinter.java  |  177 ++++
 .../core/unittestsupport/bidir/Instantiator.java   |   29 +
 .../unittestsupport/bidir/InstantiatorMap.java     |   55 +
 .../unittestsupport/bidir/InstantiatorSimple.java  |   35 +
 .../core/unittestsupport/bidir/Instantiators.java  |   24 +
 .../isis/core/unittestsupport/bidir/Parent.java    |   91 ++
 .../core/unittestsupport/bidir/ReflectUtils.java   |   81 ++
 .../core/unittestsupport/bidir/StringUtils.java    |   37 +
 .../BidirectionalRelationshipContractTestAll.java  |   18 +
 .../unittestsupport/bidir/ChildDomainObject.java   |   76 ++
 .../bidir/InstantiatorForChildDomainObject.java    |   36 +
 .../unittestsupport/bidir/ParentDomainObject.java  |   65 ++
 .../unittestsupport/bidir/PeerDomainObject.java    |  103 ++
 .../bidir/PeerDomainObjectForTesting.java          |   23 +
 .../main/java/objstore/jdo/todo/ToDoItemsJdo.java  |    1 -
 19 files changed, 1732 insertions(+), 1 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/pom.xml
----------------------------------------------------------------------
diff --git a/core/unittestsupport/pom.xml b/core/unittestsupport/pom.xml
index 70cd28f..b329cb4 100644
--- a/core/unittestsupport/pom.xml
+++ b/core/unittestsupport/pom.xml
@@ -93,6 +93,18 @@
                <artifactId>jmock-legacy</artifactId>
            </dependency>
 
+            <dependency>
+                <groupId>javax.jdo</groupId>
+                <artifactId>jdo-api</artifactId>
+                <version>3.0.1</version>
+                <optional>true</optional>
+            </dependency>
+
+            <dependency>
+                <groupId>org.reflections</groupId>
+                <artifactId>reflections</artifactId>
+            </dependency>
+
 	</dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAbstract.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAbstract.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAbstract.java
new file mode 100644
index 0000000..2d7d92c
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAbstract.java
@@ -0,0 +1,789 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Set;
+
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.common.io.ByteStreams;
+
+import org.hamcrest.Matchers;
+import org.junit.Test;
+import org.reflections.ReflectionUtils;
+import org.reflections.Reflections;
+
+public abstract class BidirectionalRelationshipContractTestAbstract implements Instantiators {
+
+    private final Reflections reflections;
+    private final InstantiatorMap instantiatorMap;
+    private IndentPrinter out;
+    
+    protected BidirectionalRelationshipContractTestAbstract(
+            final String packagePrefix, 
+            ImmutableMap<Class<?>,Instantiator> instantiatorsByClass) {
+        reflections = new Reflections(packagePrefix);
+        instantiatorMap = new InstantiatorMap(instantiatorsByClass);
+        out = new IndentPrinter(new PrintWriter(ByteStreams.nullOutputStream()));
+    }
+
+    public Instantiators withLoggingTo(Writer out) {
+        this.out = new IndentPrinter(out);
+        return this;
+    }
+    
+    public Instantiators withLoggingTo(PrintStream out) {
+        this.out = new IndentPrinter(new PrintWriter(out));
+        return this;
+    }
+
+    @Test
+    public void searchAndTest() throws Exception {
+        
+        Set<Class<?>> entityTypes =
+                Sets.newTreeSet(new Comparator<Class<?>>() {
+
+                    @Override
+                    public int compare(Class<?> o1, Class<?> o2) {
+                        return o1.getName().compareTo(o2.getName());
+                    }
+                });
+        entityTypes.addAll(reflections.getTypesAnnotatedWith(PersistenceCapable.class));
+        
+        for (Class<?> entityType : entityTypes) {
+            out.println(entityType.getName());
+            out.incrementIndent();
+            try {
+                process(entityType);
+            } finally {
+                out.decrementIndent();
+            }
+        }
+        out.println("DONE");
+        out.flush();
+    }
+
+    private void process(Class<?> entityType) {
+        final Set<Field> mappedByFields = Reflections.getAllFields(entityType, ReflectUtils.persistentMappedBy);
+        for (Field mappedByField : mappedByFields) {
+            final Parent p = new Parent();
+            p.entityType = entityType;
+            p.childField = mappedByField;
+            try {
+                out.println("processing " + p.entityType.getSimpleName() + "#" + p.childField.getName());
+                out.incrementIndent();
+                process(p);
+            } finally {
+                out.decrementIndent();
+            }
+        }
+    }
+    
+    private void process(Parent p) {
+
+        // mappedBy
+        final Persistent persistentAnnotation = p.childField.getAnnotation(Persistent.class);
+        p.mappedBy = persistentAnnotation.mappedBy();
+
+        // getMethod
+        final String getMethod = StringUtils.methodNamed("get", p.childField);
+        final Set<Method> getMethods = Reflections.getAllMethods(p.entityType, ReflectionUtils.withName(getMethod));
+        assertThat(p.desc() + ": no getXxx() method", getMethods.size(), is(1));
+        p.getMethod = CollectUtils.firstIn(getMethods);
+        
+        final Child c = new Child();
+
+        final Class<?> returnType = p.getMethod.getReturnType();
+        if(Collection.class.isAssignableFrom(returnType)) {
+            // addToMethod
+            final String addToMethod = StringUtils.methodNamed("addTo", p.childField);
+            final Set<Method> addToMethods = Reflections.getAllMethods(p.entityType, 
+                    Predicates.and(ReflectionUtils.withName(addToMethod), ReflectionUtils.withParametersCount(1), ReflectUtils.withEntityParameter()));
+            if(addToMethods.size() != 1) {
+                // just skip
+                out.println("no addToXxx() method in parent");
+                return;
+            }
+            p.addToMethod = CollectUtils.firstIn(addToMethods);
+
+            // removeFromMethod
+            final String removeFromMethod = StringUtils.methodNamed("removeFrom", p.childField);
+            final Set<Method> removeFromMethods = Reflections.getAllMethods(p.entityType, 
+                    Predicates.and(ReflectionUtils.withName(removeFromMethod), ReflectionUtils.withParametersCount(1), ReflectUtils.withEntityParameter()));
+            if(removeFromMethods.size() != 1) {
+                // just skip
+                out.println("no removeFromXxx() method in parent");
+                return;
+            }
+            p.removeFromMethod = CollectUtils.firstIn(removeFromMethods);
+
+            // child's entityType
+            final Class<?> addToParameterType = p.addToMethod.getParameterTypes()[0];
+            final Class<?> removeFromParameterType = p.removeFromMethod.getParameterTypes()[0];
+            
+            assertThat(p.desc() + ": " + p.addToMethod.getName() + " and " + p.removeFromMethod.getName() + " should have the same parameter type",
+                    addToParameterType == removeFromParameterType, is(true));
+        
+            c.entityType = addToParameterType;
+        } else {
+            
+            // modify
+            String modifyMethod = StringUtils.methodNamed("modify", p.childField);
+            final Set<Method> modifyMethods = Reflections.getAllMethods(p.entityType, 
+                    Predicates.and(Reflections.withName(modifyMethod), ReflectionUtils.withParametersCount(1), ReflectUtils.withEntityParameter()));
+            if(modifyMethods.size() != 1) {
+                // just skip
+                out.println("no modifyXxx() method in parent");
+                return;
+            }
+            p.modifyMethod = CollectUtils.firstIn(modifyMethods);
+            
+            // clear
+            String clearMethod = StringUtils.methodNamed("clear", p.childField);
+            final Set<Method> clearMethods = Reflections.getAllMethods(p.entityType, 
+                    Predicates.and(Reflections.withName(clearMethod), ReflectionUtils.withParametersCount(0)));
+            if(clearMethods.size() != 1) {
+                // just skip
+                out.println("no clearXxx() method in parent");
+                return;
+            }
+            p.clearMethod = CollectUtils.firstIn(clearMethods);
+
+            // child's entityType
+            c.entityType = p.modifyMethod.getParameterTypes()[0];
+        }
+        
+        final Instantiator parentInstantiator = instantiatorFor(p.entityType);
+        if(parentInstantiator == null) {
+            out.println("no instantiator for " + p.entityType.getName());
+            // just skip
+            return;
+        }
+        final Instantiator childInstantiator = instantiatorFor(c.entityType);
+        if(childInstantiator == null) {
+            out.println("no instantiator for " + c.entityType.getName());
+            // just skip
+            return;
+        }
+        
+        process(p, c);
+    }
+
+    private Instantiator instantiatorFor(final Class<?> cls) {
+        Instantiator instantiator = instantiatorMap.get(cls);
+        if(instantiator != null) {
+            return instantiator;
+        }
+        
+        instantiator = doInstantiatorFor(cls);
+        
+        instantiator = instantiatorMap.put(cls, instantiator);
+        return instantiator != Instantiator.NOOP? instantiator: null;
+    }
+
+
+    /**
+     * Default just tries to use the {@link InstantiatorSimple};
+     * subclasses can override with more sophisticated implementations if required.
+     */
+    protected Instantiator doInstantiatorFor(final Class<?> cls) {
+        return new InstantiatorSimple(cls);
+    }
+
+    private void process(Parent p, Child c) {
+        
+        // mappedBy field
+        final Set<Field> parentFields = Reflections.getAllFields(c.entityType, Predicates.and(Reflections.withName(p.mappedBy), ReflectUtils.withTypeAssignableFrom(p.entityType)));
+
+        assertThat(c.entityType.getName()+  ": could not locate '" + p.mappedBy + "' field, returning supertype of " + p.entityType.getSimpleName() +", (as per @Persistent(mappedBy=...) in parent "+ p.entityType.getSimpleName()+")", parentFields.size(), is(1));
+        c.parentField = CollectUtils.firstIn(parentFields);
+
+        // getter
+        String getterMethod = StringUtils.methodNamed("get", c.parentField);
+        final Set<Method> getterMethods = Reflections.getAllMethods(c.entityType, 
+                Predicates.and(Reflections.withName(getterMethod), ReflectionUtils.withParametersCount(0), ReflectUtils.withReturnTypeAssignableFrom(p.entityType)));
+        assertThat(p.descRel(c) +": could not locate getter " + getterMethod + "() returning supertype of " + p.entityType.getSimpleName(), getterMethods.size(), is(1));
+        c.getMethod = CollectUtils.firstIn(getterMethods);
+
+        // modify
+        String modifyMethod = StringUtils.methodNamed("modify", c.parentField);
+        final Set<Method> modifyMethods = Reflections.getAllMethods(c.entityType, 
+                Predicates.and(Reflections.withName(modifyMethod), ReflectionUtils.withParametersCount(1), ReflectUtils.withParametersAssignableFrom(p.entityType)));
+        if(modifyMethods.size() != 1) {
+            // just skip
+            out.println("no modifyXxx() method in child");
+            return;
+        }
+        c.modifyMethod = CollectUtils.firstIn(modifyMethods);
+        
+        // clear
+        String clearMethod = StringUtils.methodNamed("clear", c.parentField);
+        final Set<Method> clearMethods = Reflections.getAllMethods(c.entityType, 
+                Predicates.and(Reflections.withName(clearMethod), ReflectionUtils.withParametersCount(0)));
+        if(clearMethods.size() != 1) {
+            // just skip
+            out.println("no clearXxx() method in child");
+            return;
+        }
+        c.clearMethod = CollectUtils.firstIn(clearMethods);
+
+        exercise(p, c);
+    }
+
+    @Override
+    public Object newInstance(final Class<?> entityType) {
+        final Instantiator instantiator = instantiatorFor(entityType);
+        return instantiator.instantiate();
+    }
+
+    private static String assertDesc(Parent p, Child c, String methodDesc, String testDesc) {
+        return p.descRel(c) +": " + methodDesc + ": " + testDesc;
+    }
+
+    private void exercise(Parent p, Child c) {
+        out.println("exercising " + p.descRel(c));
+        out.incrementIndent();
+        try {
+            if(p.addToMethod != null) {
+                // 1:m
+                
+                // add
+                oneToManyParentAddTo(p, c);
+                oneToManyParentAddToWhenAlreadyChild(p, c);
+                oneToManyParentAddToWhenNull(p, c);
+                oneToManyChildModify(p, c);
+                oneToManyChildModifyWhenAlreadyParent(p, c);
+                oneToManyChildModifyWhenNull(p, c);
+                
+                // move (update)
+                oneToManyChildModifyToNewParent(p, c);
+                oneToManyChildModifyToExistingParent(p, c);
+                
+                // delete
+                oneToManyParentRemoveFrom(p, c);
+                oneToManyParentRemoveFromWhenNotAssociated(p, c);
+                oneToManyParentRemoveFromWhenNull(p, c);
+                oneToManyChildClear(p, c);
+                oneToManyChildClearWhenNotAssociated(p, c);
+            } else {
+                // 1:1
+
+                // add
+                oneToOneParentModify(p, c);
+                oneToOneParentModifyWhenAlreadyChild(p, c);
+                oneToOneParentModifyWhenNull(p, c);
+                oneToOneChildModify(p, c);
+                oneToOneChildModifyWhenAlreadyParent(p, c);
+                oneToOneChildModifyWhenNull(p, c);
+                
+                // move (update)
+                oneToOneChildModifyToNewParent(p, c);
+                oneToOneChildModifyToExistingParent(p, c);
+                
+                // delete
+                oneToOneParentClear(p, c);
+                oneToOneChildClear(p, c);
+                oneToOneChildClearWhenNotAssociated(p, c);
+            }
+            
+        } finally {
+            out.decrementIndent();
+        }
+
+    }
+
+    ////////////////
+    // 1:m
+    ////////////////
+    
+    private void oneToManyParentAddTo(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyParentAddTo";
+        out.println(methodDesc);
+        
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        // when
+        p.addToChildren(parent1, child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent contains child"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child references parent"), c.getParent(child1), is(parent1));
+    }
+
+    private void oneToManyParentAddToWhenAlreadyChild(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyParentAddToWhenAlreadyChild";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        // given
+        p.addToChildren(parent1, child1);
+        
+        // when
+        p.addToChildren(parent1, child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still contains child"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child still references parent"), c.getParent(child1), is(parent1));
+    }
+    
+    private void oneToManyParentAddToWhenNull(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyParentAddToWhenNull";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+
+        // when
+        p.addToChildren(parent1, null);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent does not have any children"), p.getChildren(parent1).isEmpty(), is(true));
+    }
+    
+    private void oneToManyChildModify(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyChildModify";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        // when
+        c.modifyParent(child1, parent1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent contains child"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child references parent"), c.getParent(child1), is(parent1));
+    }
+
+    private void oneToManyChildModifyWhenAlreadyParent(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyChildModifyWhenAlreadyParent";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        c.modifyParent(child1, parent1);
+        
+        // when
+        c.modifyParent(child1, parent1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still contains child"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child still references parent"), c.getParent(child1), is(parent1));
+    }
+
+
+    private void oneToManyChildModifyWhenNull(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyChildModifyWhenNull";
+        out.println(methodDesc);
+
+        // given
+        Object child1 = c.newChild(this);
+
+        // when
+        c.modifyParent(child1, null);
+        
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"child does not reference any parent"), c.getParent(child1), is(nullValue()));
+    }
+    
+
+    private void oneToManyChildModifyToNewParent(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyChildModifyToNewParent";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object parent2 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        Object child2 = c.newChild(this);
+        
+        p.addToChildren(parent1, child1);
+        p.addToChildren(parent2, child2);
+        
+        // when
+        c.modifyParent(child1, parent2);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent 1 no longer has any children"), p.getChildren(parent1).isEmpty(), is(true));
+        assertThat(assertDesc(p,c,methodDesc,"parent 2 now has both children"), p.getChildren(parent2), Matchers.containsInAnyOrder(child1, child2));
+        assertThat(assertDesc(p,c,methodDesc,"child 1 now references parent 2"), c.getParent(child1), is(parent2));
+        assertThat(assertDesc(p,c,methodDesc,"child 2 still references parent 2"), c.getParent(child2), is(parent2));
+    }
+    
+    private void oneToManyChildModifyToExistingParent(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyChildModifyToExistingParent";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object parent2 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        Object child2 = c.newChild(this);
+        
+        p.addToChildren(parent1, child1);
+        p.addToChildren(parent2, child2);
+        
+        // when
+        c.modifyParent(child1, parent1);
+        
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent 1 still contains child 1"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"parent 2 still contains child 2"), p.getChildren(parent2), Matchers.containsInAnyOrder(child2));
+        assertThat(assertDesc(p,c,methodDesc,"child 1 still references parent 1"), c.getParent(child1), is(parent1));
+        assertThat(assertDesc(p,c,methodDesc,"child 2 still references parent 2"), c.getParent(child2), is(parent2));
+    }
+
+    private void oneToManyParentRemoveFrom(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyParentRemoveFrom";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        p.addToChildren(parent1, child1);
+        
+        // when
+        p.removeFromChildren(parent1, child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent no longer contains child"), p.getChildren(parent1).isEmpty(), is(true));
+        assertThat(assertDesc(p,c,methodDesc,"child no longer references parent"), c.getParent(child1), is(nullValue()));
+    }
+
+    private void oneToManyParentRemoveFromWhenNull(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyParentRemoveFromWhenNull";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        p.addToChildren(parent1, child1);
+        
+        // when
+        p.removeFromChildren(parent1, null);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still contains child"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child still references parent"), c.getParent(child1), is(parent1));
+    }
+    
+    private void oneToManyParentRemoveFromWhenNotAssociated(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyParentRemoveFromWhenNotAssociated";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        Object child2 = c.newChild(this);
+        
+        p.addToChildren(parent1, child1);
+        
+        // when
+        p.removeFromChildren(parent1, child2);
+        
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still contains child"), p.getChildren(parent1), Matchers.containsInAnyOrder(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child still references parent"), c.getParent(child1), is(parent1));
+    }
+    
+    private void oneToManyChildClear(Parent p, Child c) {
+        
+        final String methodDesc = "oneToManyChildClear";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        p.addToChildren(parent1, child1);
+        
+        // when
+        c.clearParent(child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent no longer contains child"), p.getChildren(parent1).isEmpty(), is(true));
+        assertThat(assertDesc(p,c,methodDesc,"child no longer references parent"), c.getParent(child1), is(nullValue()));
+    }
+
+    private void oneToManyChildClearWhenNotAssociated(Parent p, Child c) {
+
+        final String methodDesc = "oneToManyChildClearWhenNotAssociated";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        // when
+        c.clearParent(child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still does not reference child"), p.getChildren(parent1).isEmpty(), is(true));
+        assertThat(assertDesc(p,c,methodDesc,"child still does not reference parent"), c.getParent(child1), is(nullValue()));
+    }
+
+
+    
+    ////////////////
+    // 1:1
+    ////////////////
+    
+    private void oneToOneParentModify(Parent p, Child c) {
+        
+        final String methodDesc = "oneToOneParentModify";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        // when
+        p.modifyChild(parent1, child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent references child"),p.getChild(parent1), is(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child references parent"), c.getParent(child1), is(parent1));
+    }
+
+    private void oneToOneParentModifyWhenAlreadyChild(Parent p, Child c) {
+        
+        final String methodDesc = "oneToOneParentModifyWhenAlreadyChild";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        p.modifyChild(parent1, child1);
+        
+        // when
+        p.modifyChild(parent1, child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still references child"), p.getChild(parent1), is(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child still references parent"), c.getParent(child1), is(parent1));
+    }
+    
+    private void oneToOneParentModifyWhenNull(Parent p, Child c) {
+
+        final String methodDesc = "oneToOneParentModifyWhenNull";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+
+        // when
+        p.modifyChild(parent1, null);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still references child"), p.getChild(parent1), is(nullValue()));
+    }
+    
+    private void oneToOneChildModify(Parent p, Child c) {
+
+        final String methodDesc = "oneToOneChildModify";
+        out.println(methodDesc);
+        
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        // when
+        c.modifyParent(child1, parent1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent references child"), p.getChild(parent1), is(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child references parent"), c.getParent(child1), is(parent1));
+    }
+
+    private void oneToOneChildModifyWhenAlreadyParent(Parent p, Child c) {
+
+        final String methodDesc = "oneToOneChildModifyWhenAlreadyParent";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+
+        c.modifyParent(child1, parent1);
+        
+        // when
+        c.modifyParent(child1, parent1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still references child"), p.getChild(parent1), is(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child still references parent"), c.getParent(child1), is(parent1));
+    }
+
+
+    private void oneToOneChildModifyWhenNull(Parent p, Child c) {
+
+        final String methodDesc = "oneToOneChildModifyWhenNull";
+        out.println(methodDesc);
+
+        // given
+        Object child1 = c.newChild(this);
+
+        // when
+        c.modifyParent(child1, null);
+        
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"child still has no parent"), c.getParent(child1), is(nullValue()));
+    }
+    
+
+    private void oneToOneChildModifyToNewParent(Parent p, Child c) {
+        
+        final String methodDesc = "oneToOneChildModifyToNewParent";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object parent2 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        Object child2 = c.newChild(this);
+        
+        p.modifyChild(parent1, child1);
+        p.modifyChild(parent2, child2);
+        
+        // when
+        c.modifyParent(child1, parent2);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent 1 no longer references child 1"), p.getChild(parent1), is(nullValue()));
+        assertThat(assertDesc(p,c,methodDesc,"parent 2 now references child 1"), p.getChild(parent2), is(child1));
+        assertThat(assertDesc(p,c,methodDesc,"child 1 now references parent 2"), c.getParent(child1), is(parent2));
+        assertThat(assertDesc(p,c,methodDesc,"child 2, as a side-effect, no longer references parent 2"), c.getParent(child2), is(nullValue()));
+    }
+    
+    private void oneToOneChildModifyToExistingParent(Parent p, Child c) {
+        
+        final String methodDesc = "oneToOneChildModifyToExistingParent";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object parent2 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        Object child2 = c.newChild(this);
+        
+        p.modifyChild(parent1, child1);
+        p.modifyChild(parent2, child2);
+        
+        // when
+        c.modifyParent(child1, parent1);
+        
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent 1 still references child 1"), p.getChild(parent1), is(child1));
+        assertThat(assertDesc(p,c,methodDesc,"parent 2 still references child 2"), p.getChild(parent2), is(child2));
+        assertThat(assertDesc(p,c,methodDesc,"child 1 still references parent 1"), c.getParent(child1), is(parent1));
+        assertThat(assertDesc(p,c,methodDesc,"child 2 still references parent 2"), c.getParent(child2), is(parent2));
+    }
+
+    private void oneToOneParentClear(Parent p, Child c) {
+
+        final String methodDesc = "oneToOneParentClear";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        p.modifyChild(parent1, child1);
+        
+        // when
+        p.clearChild(parent1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent no longer references child"), p.getChild(parent1), is(nullValue()));
+        assertThat(assertDesc(p,c,methodDesc,"child no longer references parent"), c.getParent(child1), is(nullValue()));
+    }
+
+    
+    private void oneToOneChildClear(Parent p, Child c) {
+        
+        final String methodDesc = "oneToOneChildClear";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        p.modifyChild(parent1, child1);
+        
+        // when
+        c.clearParent(child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent no longer references child"), p.getChild(parent1), is(nullValue()));
+        assertThat(assertDesc(p,c,methodDesc,"child no longer references parent"), c.getParent(child1), is(nullValue()));
+    }
+
+    private void oneToOneChildClearWhenNotAssociated(Parent p, Child c) {
+        
+        final String methodDesc = "oneToOneChildClearWhenNotAssociated";
+        out.println(methodDesc);
+
+        // given
+        Object parent1 = p.newParent(this);
+        Object child1 = c.newChild(this);
+        
+        // when
+        c.clearParent(child1);
+
+        // then
+        assertThat(assertDesc(p,c,methodDesc,"parent still does not reference child"), p.getChild(parent1), is(nullValue()));
+        assertThat(assertDesc(p,c,methodDesc,"child still does not reference parent"), c.getParent(child1), is(nullValue()));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Child.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Child.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Child.java
new file mode 100644
index 0000000..4585bed
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Child.java
@@ -0,0 +1,52 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+class Child {
+    Class<?> entityType;
+    Field parentField;
+    Method getMethod;
+    Method modifyMethod;
+    Method clearMethod;
+    Object getParent(Object child) {
+        try {
+            return getMethod.invoke(child);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    void modifyParent(Object child, Object parent) {
+        try {
+            modifyMethod.invoke(child, parent);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    void clearParent(Object child) {
+        try {
+            clearMethod.invoke(child);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    Object newChild(Instantiators instantiators) {
+        return instantiators.newInstance(entityType);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/CollectUtils.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/CollectUtils.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/CollectUtils.java
new file mode 100644
index 0000000..d23003b
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/CollectUtils.java
@@ -0,0 +1,29 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.lang.reflect.Field;
+import java.util.Set;
+
+class CollectUtils {
+
+    static <T> T firstIn(final Set<T> set) {
+        return set.iterator().next();
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/IndentPrinter.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/IndentPrinter.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/IndentPrinter.java
new file mode 100644
index 0000000..aa10637
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/IndentPrinter.java
@@ -0,0 +1,177 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * Adapted from <tt>groovy.util.IndentPrinter</tt> (published under ASL 2.0).
+ */
+class IndentPrinter {
+
+    private int indentLevel;
+    private String indent;
+    private Writer out;
+    private final boolean addNewlines;
+
+    /**
+     * Creates an IndentPrinter backed by a PrintWriter pointing to System.out, with an indent of two spaces.
+     *
+     * @see #IndentPrinter(Writer, String)
+     */
+    public IndentPrinter() {
+        this(new PrintWriter(System.out), "  ");
+    }
+
+    /**
+     * Creates an IndentPrinter backed by the supplied Writer, with an indent of two spaces.
+     *
+     * @param out Writer to output to
+     * @see #IndentPrinter(Writer, String)
+     */
+    public IndentPrinter(Writer out) {
+        this(out, "  ");
+    }
+
+    /**
+     * Creates an IndentPrinter backed by the supplied Writer,
+     * with a user-supplied String to be used for indenting.
+     *
+     * @param out Writer to output to
+     * @param indent character(s) used to indent each line
+     */
+    public IndentPrinter(Writer out, String indent) {
+        this(out, indent, true);
+    }
+
+    /**
+     * Creates an IndentPrinter backed by the supplied Writer,
+     * with a user-supplied String to be used for indenting
+     * and the ability to override newline handling.
+     *
+     * @param out Writer to output to
+     * @param indent character(s) used to indent each line
+     * @param addNewlines set to false to gobble all new lines (default true)
+     */
+    public IndentPrinter(Writer out, String indent, boolean addNewlines) {
+        this.addNewlines = addNewlines;
+        if (out == null) {
+            throw new IllegalArgumentException("Must specify a Writer");
+        }
+        this.out = out;
+        this.indent = indent;
+    }
+
+    /**
+     * Prints a string followed by an end of line character.
+     *
+     * @param  text String to be written
+     */
+    public void println(String text) {
+        printIndent();
+        try {
+            out.write(text);
+            println();
+            flush();
+        } catch(IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    /**
+     * Prints a string.
+     *
+     * @param  text String to be written
+     */
+    public void print(String text) {
+        try {
+            out.write(text);
+        } catch(IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    /**
+     * Prints a character.
+     *
+     * @param  c char to be written
+     */
+    public void print(char c) {
+        try {
+            out.write(c);
+        } catch(IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+
+    /**
+     * Prints the current indent level.
+     */
+    public void printIndent() {
+        for (int i = 0; i < indentLevel; i++) {
+            try {
+                out.write(indent);
+            } catch(IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+
+    /**
+     * Prints an end-of-line character (if enabled via addNewLines property).
+     * Defaults to outputting a single '\n' character but by using a custom
+     * Writer, e.g. PlatformLineWriter, you can get platform-specific
+     * end-of-line characters.
+     *
+     * @see #IndentPrinter(Writer, String, boolean)
+     */
+    public void println() {
+        if (addNewlines) {
+            try {
+                out.write("\n");
+            } catch(IOException ioe) {
+                throw new RuntimeException(ioe);
+            }
+        }
+    }
+
+    public void incrementIndent() {
+        ++indentLevel;
+    }
+
+    public void decrementIndent() {
+        --indentLevel;
+    }
+
+    public int getIndentLevel() {
+        return indentLevel;
+    }
+
+    public void setIndentLevel(int indentLevel) {
+        this.indentLevel = indentLevel;
+    }
+
+    public void flush() {
+        try {
+            out.flush();
+        } catch(IOException ioe) {
+            throw new RuntimeException(ioe);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiator.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiator.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiator.java
new file mode 100644
index 0000000..9678dbc
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiator.java
@@ -0,0 +1,29 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+public interface Instantiator {
+    Instantiator NOOP = new Instantiator() {
+        
+        @Override
+        public Object instantiate() {
+            return null;
+        }
+    };
+
+    public Object instantiate();
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorMap.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorMap.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorMap.java
new file mode 100644
index 0000000..b4e661c
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorMap.java
@@ -0,0 +1,55 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+class InstantiatorMap  {
+
+    private Map<Class<?>, Instantiator> instantiatorMap;
+
+    public InstantiatorMap(ImmutableMap<Class<?>, Instantiator> instantiatorMap) {
+        this.instantiatorMap = Maps.newHashMap(instantiatorMap);
+    }
+
+    Instantiator get(Class<?> cls) {
+        return instantiatorMap.get(cls);
+    }
+
+    Instantiator put(Class<?> cls, Instantiator instantiator) {
+        
+        if(instantiator != null) {
+            // check it works instantiator
+            try {
+                @SuppressWarnings("unused")
+                final Object dummy = instantiator.instantiate();
+            } catch(RuntimeException ex) {
+                instantiator = Instantiator.NOOP;
+            }
+        } else {
+            instantiator = Instantiator.NOOP;
+        }
+        
+        instantiatorMap.put(cls, instantiator);
+        
+        return instantiator;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorSimple.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorSimple.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorSimple.java
new file mode 100644
index 0000000..8ae8267
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorSimple.java
@@ -0,0 +1,35 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+public class InstantiatorSimple implements Instantiator {
+
+    public final Class<?> cls;
+    
+    public InstantiatorSimple(Class<?> cls) {
+        this.cls = cls;
+    }
+
+    @Override
+    public Object instantiate() {
+        try {
+            return cls.newInstance();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiators.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiators.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiators.java
new file mode 100644
index 0000000..d2ea382
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Instantiators.java
@@ -0,0 +1,24 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+interface Instantiators {
+
+    public abstract Object newInstance(Class<?> entityType);
+
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Parent.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Parent.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Parent.java
new file mode 100644
index 0000000..fafb638
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/Parent.java
@@ -0,0 +1,91 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+class Parent {
+    Class<?> entityType;
+    Field childField;
+    String mappedBy;
+    Method getMethod;
+    
+    // for 1:m
+    Method addToMethod;
+    Method removeFromMethod;
+    
+    // if 1:1
+    Method modifyMethod;
+    Method clearMethod;
+    
+    Collection<?> getChildren(Object parent) throws RuntimeException {
+        try {
+            return (Collection<?>) getMethod.invoke(parent);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    void addToChildren(Object parent, Object child) {
+        try {
+            addToMethod.invoke(parent, child);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    void removeFromChildren(Object parent, Object child) {
+        try {
+            removeFromMethod.invoke(parent, child);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    Object getChild(Object parent) {
+        try {
+            return getMethod.invoke(parent);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    void modifyChild(Object parent, Object child) {
+        try {
+            modifyMethod.invoke(parent, child);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    void clearChild(Object parent) {
+        try {
+            clearMethod.invoke(parent);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    String desc() {
+        return entityType.getName()+"#"+childField.getName();
+    }
+    String descRel(Child c) {
+        final boolean oneToMany = addToMethod != null;
+        return entityType.getSimpleName() + "#" + childField.getName() + " 1:" + (oneToMany?"m":"1") + " "+ c.entityType.getSimpleName() + "#" + c.parentField.getName();
+    }
+    
+    Object newParent(Instantiators instantiators) {
+        return instantiators.newInstance(entityType);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/ReflectUtils.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/ReflectUtils.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/ReflectUtils.java
new file mode 100644
index 0000000..dff7615
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/ReflectUtils.java
@@ -0,0 +1,81 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Strings;
+
+class ReflectUtils {
+
+    public static <T> Predicate<Field> withTypeAssignableFrom(final Class<T> type) {
+        return new Predicate<Field>() {
+            public boolean apply(Field input) {
+                return input != null && input.getType().isAssignableFrom(type);
+            }
+        };
+    }
+
+    public static <T> Predicate<Method> withReturnTypeAssignableFrom(final Class<T> type) {
+        return new Predicate<Method>() {
+            public boolean apply(Method input) {
+                return input != null && input.getReturnType().isAssignableFrom(type);
+            }
+        };
+    }
+
+    public static Predicate<Method> withParametersAssignableFrom(final Class<?>... types) {
+        return new Predicate<Method>() {
+            public boolean apply(Method input) {
+                if (input != null) {
+                    Class<?>[] parameterTypes = input.getParameterTypes();
+                    if (parameterTypes.length == types.length) {
+                        for (int i = 0; i < parameterTypes.length; i++) {
+                            if (!parameterTypes[i].isAssignableFrom(types[i])) {
+                                return false;
+                            }
+                        }
+                        return true;
+                    }
+                }
+                return false;
+            }
+        };
+    }
+
+    static final Predicate<? super Field> persistentMappedBy = new Predicate<Field>() {
+        public boolean apply(Field f) {
+            final Persistent annotation = f.getAnnotation(Persistent.class);
+            return annotation!=null && !Strings.isNullOrEmpty(annotation.mappedBy());
+        }
+    };
+
+    static Predicate<? super Method> withEntityParameter() {
+        return new Predicate<Method>() {
+            public boolean apply(Method m) {
+                final Class<?> parameterType = m.getParameterTypes()[0];
+                return parameterType.isAnnotationPresent(PersistenceCapable.class);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/StringUtils.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/StringUtils.java b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/StringUtils.java
new file mode 100644
index 0000000..23e9b4a
--- /dev/null
+++ b/core/unittestsupport/src/main/java/org/apache/isis/core/unittestsupport/bidir/StringUtils.java
@@ -0,0 +1,37 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.lang.reflect.Field;
+
+class StringUtils {
+
+    public static String capitalize(final String str) {
+        if (str == null || str.length() == 0) {
+            return str;
+        }
+        if (str.length() == 1) {
+            return str.toUpperCase();
+        }
+        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
+    }
+
+    public static String methodNamed(final String methodPrefix, final Field field) {
+        return methodPrefix + capitalize(field.getName());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAll.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAll.java b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAll.java
new file mode 100644
index 0000000..4e4c77c
--- /dev/null
+++ b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/BidirectionalRelationshipContractTestAll.java
@@ -0,0 +1,18 @@
+package org.apache.isis.core.unittestsupport.bidir;
+
+import com.google.common.collect.ImmutableMap;
+
+public class BidirectionalRelationshipContractTestAll extends BidirectionalRelationshipContractTestAbstract {
+
+    public BidirectionalRelationshipContractTestAll() {
+        super("org.apache.isis.core.unittestsupport.bidir", 
+                ImmutableMap.<Class<?>,Instantiator>of(
+                    // no instantiator need be registered for ParentDomainObject.class; 
+                    // will default to using new InstantiatorSimple(AgreementForTesting.class),
+                    ChildDomainObject.class, new InstantiatorForChildDomainObject(),
+                    PeerDomainObject.class, new InstantiatorSimple(PeerDomainObjectForTesting.class)
+                ));
+        withLoggingTo(System.out);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ChildDomainObject.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ChildDomainObject.java b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ChildDomainObject.java
new file mode 100644
index 0000000..9d8ea06
--- /dev/null
+++ b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ChildDomainObject.java
@@ -0,0 +1,76 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import javax.jdo.annotations.PersistenceCapable;
+
+
+@PersistenceCapable
+public class ChildDomainObject implements Comparable<ChildDomainObject> {
+
+    // {{ Index (property)
+    private int index;
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(final int index) {
+        this.index = index;
+    }
+    // }}
+
+    
+    // {{ Parent (property)
+    private ParentDomainObject parent;
+
+    public ParentDomainObject getParent() {
+        return parent;
+    }
+
+    public void setParent(final ParentDomainObject parent) {
+        this.parent = parent;
+    }
+    public void modifyParent(final ParentDomainObject parent) {
+        ParentDomainObject currentParent = getParent();
+        // check for no-op
+        if (parent == null || parent.equals(currentParent)) {
+            return;
+        }
+        // delegate to parent to associate
+        parent.addToChildren(this);
+    }
+
+    public void clearParent() {
+        ParentDomainObject currentParent = getParent();
+        // check for no-op
+        if (currentParent == null) {
+            return;
+        }
+        // delegate to parent to dissociate
+        currentParent.removeFromChildren(this);
+    }
+    // }}
+
+
+
+    @Override
+    public int compareTo(ChildDomainObject other) {
+        return this.getIndex() - other.getIndex();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorForChildDomainObject.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorForChildDomainObject.java b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorForChildDomainObject.java
new file mode 100644
index 0000000..a45a604
--- /dev/null
+++ b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/InstantiatorForChildDomainObject.java
@@ -0,0 +1,36 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.util.SortedSet;
+
+/**
+ * To ensure that different ({@link Comparable}) {@link ChildDomainObject}s
+ * are not equivalent when placed into the {@link ParentDomainObject#getChildren() children} (a {@link SortedSet}.) 
+ */
+public class InstantiatorForChildDomainObject implements Instantiator {
+
+    private int i;
+    
+    @Override
+    public Object instantiate() {
+        final ChildDomainObject cdo = new ChildDomainObject();
+        cdo.setIndex(++i);
+        return cdo;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ParentDomainObject.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ParentDomainObject.java b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ParentDomainObject.java
new file mode 100644
index 0000000..591f230
--- /dev/null
+++ b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/ParentDomainObject.java
@@ -0,0 +1,65 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+
+@PersistenceCapable
+public class ParentDomainObject {
+
+    // {{ Children (Collection)
+    @Persistent(mappedBy="parent")
+    private SortedSet<ChildDomainObject> children = new TreeSet<ChildDomainObject>();
+
+    public SortedSet<ChildDomainObject> getChildren() {
+        return children;
+    }
+
+    public void setChildren(final SortedSet<ChildDomainObject> children) {
+        this.children = children;
+    }
+    
+    public void addToChildren(final ChildDomainObject child) {
+        // check for no-op
+        if (child == null || getChildren().contains(child)) {
+            return;
+        }
+        // dissociate arg from its current parent (if any).
+        child.clearParent();
+        // associate arg
+        child.setParent(this);
+        getChildren().add(child);
+    }
+
+    public void removeFromChildren(final ChildDomainObject child) {
+        // check for no-op
+        if (child == null || !getChildren().contains(child)) {
+            return;
+        }
+        // dissociate arg
+        child.setParent(null);
+        getChildren().remove(child);
+    }
+    
+    // }}
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObject.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObject.java b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObject.java
new file mode 100644
index 0000000..56f29e3
--- /dev/null
+++ b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObject.java
@@ -0,0 +1,103 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import javax.jdo.annotations.PersistenceCapable;
+import javax.jdo.annotations.Persistent;
+
+/**
+ * Not instantiable; the {@link PeerDomainObjectForTesting} must be used in the bidir contract testing instead.
+ */
+@PersistenceCapable
+public abstract class PeerDomainObject {
+
+    // {{ Next (property)
+    @Persistent(mappedBy="previous")
+    private PeerDomainObject next;
+
+    public PeerDomainObject getNext() {
+        return next;
+    }
+
+    public void setNext(final PeerDomainObject next) {
+        this.next = next;
+    }
+    public void modifyNext(final PeerDomainObject next) {
+        PeerDomainObject currentNext = getNext();
+        // check for no-op
+        if (next == null || next.equals(currentNext)) {
+            return;
+        }
+        // dissociate existing
+        clearNext();
+        // associate new
+        next.setPrevious(this);
+        setNext(next);
+    }
+
+    public void clearNext() {
+        PeerDomainObject currentNext = getNext();
+        // check for no-op
+        if (currentNext == null) {
+            return;
+        }
+        // dissociate existing
+        currentNext.setPrevious(null);
+        setNext(null);
+    }
+    // }}
+
+
+    
+    // {{ Previous (property)
+    private PeerDomainObject previous;
+
+    public PeerDomainObject getPrevious() {
+        return previous;
+    }
+
+    public void setPrevious(final PeerDomainObject previous) {
+        this.previous = previous;
+    }
+    
+    public void modifyPrevious(final PeerDomainObject previous) {
+        PeerDomainObject currentPrevious = getPrevious();
+        // check for no-op
+        if (previous == null || previous.equals(currentPrevious)) {
+            return;
+        }
+        // delegate to parent(s) to (re-)associate
+        if (currentPrevious != null) {
+            currentPrevious.clearNext();
+        }
+        previous.modifyNext(this);
+    }
+
+    public void clearPrevious() {
+        PeerDomainObject currentPrevious = getPrevious();
+        // check for no-op
+        if (currentPrevious == null) {
+            return;
+        }
+        // delegate to parent to dissociate
+        currentPrevious.clearNext();
+    }
+    // }}
+
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObjectForTesting.java
----------------------------------------------------------------------
diff --git a/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObjectForTesting.java b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObjectForTesting.java
new file mode 100644
index 0000000..ed4fc70
--- /dev/null
+++ b/core/unittestsupport/src/test/java/org/apache/isis/core/unittestsupport/bidir/PeerDomainObjectForTesting.java
@@ -0,0 +1,23 @@
+/**
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.isis.core.unittestsupport.bidir;
+
+import javax.jdo.annotations.Persistent;
+
+public class PeerDomainObjectForTesting extends PeerDomainObject {
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/c375a4f1/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java b/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
index f5837f3..a16c228 100644
--- a/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
+++ b/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
@@ -18,7 +18,6 @@
  */
 package objstore.jdo.todo;
 
-import java.util.Collections;
 import java.util.List;
 
 import com.google.common.base.Predicate;