static <T> T copy(T object, DataComplexTable alreadyCopied) throws CloneNotSupportedException
  {
    if (object == null)
    {
      return null;
    }
    else if (isComplex(object))
    {
      DataComplex src = (DataComplex) object;

      @SuppressWarnings("unchecked")
      T found = (T) alreadyCopied.get(src);

      if (found != null)
      {
        return found;
      }
      else
      {
        DataComplex clone = src.clone();
        alreadyCopied.put(src, clone);

        if (clone instanceof DataMap)
        {
          ((DataMap)clone).copyReferencedObjects(alreadyCopied);
        }
        else if (clone instanceof DataList)
        {
          ((DataList)clone).copyReferencedObjects(alreadyCopied);
        }

        @SuppressWarnings("unchecked")
        T converted = (T) clone;
        return converted;
      }
    }
    else if (isPrimitive(object))
    {
      return object;
    }
    else
    {
      throw new CloneNotSupportedException("Illegal value encountered: " + object);
    }
  }
@Test
  public void testCopy() throws CloneNotSupportedException
  {
    boolean copyOnWrite = ! CheckedMap.class.isAssignableFrom(DataMap.class);

    /* DataMap with only immutable types */

    DataMap map1 = new DataMap(referenceMap1);
    DataMap map2 = map1.copy();
    DataMap map3 = map2.copy();
    assertTrue(! copyOnWrite || map1.getUnderlying() == map2.getUnderlying());
    assertTrue(! copyOnWrite || map1.getUnderlying() == map3.getUnderlying());
    assertEquals(map1, map2);
    assertEquals(map1, map3);

    map2.put("2", "2");
    assertTrue(! copyOnWrite || map1.getUnderlying() != map2.getUnderlying());
    assertTrue(! copyOnWrite || map1.getUnderlying() == map3.getUnderlying());
    assertFalse(map1.equals(map2));
    assertEquals(map1, map3);

    /* DataMap containing DataMap */

    DataMap map2_1 = new DataMap(referenceMap1);
    map2_1.put("2_1", "2_1");
    map2.put("map2_1", map2_1);
    assertTrue(map2.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());

    DataMap map4 = map2.copy();
    assertTrue(! copyOnWrite || map4.getUnderlying() != map2.getUnderlying());
    assertTrue(! copyOnWrite || map4.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertTrue(! copyOnWrite || map2.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertEquals(map4, map2);

    DataMap map5 = map4.copy();
    assertTrue(! copyOnWrite || map5.getUnderlying() != map2.getUnderlying());
    assertTrue(! copyOnWrite || map5.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertTrue(! copyOnWrite || map2.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertEquals(map5, map4);
    assertEquals(map5, map2);

    map5.getDataMap("map2_1").put("x", "x");
    assertTrue(! copyOnWrite || map5.getDataMap("map2_1").getUnderlying() != map2_1.getUnderlying());
    assertTrue(! copyOnWrite || map4.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertFalse(map5.getDataMap("map2_1").equals(map2_1));
    assertEquals(map4.getDataMap("map2_1"), map2_1);

    /* DataList with only primitive types */

    DataList list1 = new DataList(referenceList1);
    DataList list2 = list1.copy();
    DataList list3 = list2.copy();
    assertTrue(! copyOnWrite || list1.getUnderlying() == list2.getUnderlying());
    assertTrue(! copyOnWrite || list1.getUnderlying() == list3.getUnderlying());
    assertEquals(list1, list2);
    assertEquals(list1, list3);

    list2.add("2");
    assertTrue(! copyOnWrite || list1.getUnderlying() != list2.getUnderlying());
    assertTrue(! copyOnWrite || list1.getUnderlying() == list3.getUnderlying());
    assertFalse(list1.equals(list2));
    assertEquals(list1, list3);

    /* DataList containing DataList */

    DataList list2_1 = new DataList(referenceList1);
    list2_1.add("2_1");
    list2.add(0, list2_1);
    assertTrue(! copyOnWrite || list2.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertFalse(list2.equals(list1));
    assertFalse(list2.equals(list3));
    assertEquals(list1, list3);

    DataList list4 = list2.copy();
    assertTrue(! copyOnWrite || list4.getUnderlying() != list2.getUnderlying());
    assertTrue(! copyOnWrite || list4.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || list2.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertEquals(list4, list2);
    assertEquals(list4.getDataList(0), list2_1);
    assertEquals(list2.getDataList(0), list2_1);

    DataList list5 = list4.copy();
    assertTrue(! copyOnWrite || list5.getUnderlying() != list2.getUnderlying());
    assertTrue(! copyOnWrite || list5.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || list2.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertEquals(list5, list4);
    assertEquals(list5.getDataList(0), list2_1);
    assertEquals(list2.getDataList(0), list2_1);

    /* DataMap containing DataList */

    DataMap map6 = map1.copy();
    assertTrue(! copyOnWrite || map6.getUnderlying() == map1.getUnderlying());
    assertEquals(map6, map1);
    map6.put("list2_1", list2_1);
    assertTrue(! copyOnWrite || map6.getUnderlying() != map1.getUnderlying());
    assertFalse(map6.equals(map1));

    DataMap map7 = map6.copy();
    assertTrue(! copyOnWrite || map7.getUnderlying() != map6.getUnderlying());
    assertTrue(! copyOnWrite || map7.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertEquals(map7, map6);
    assertEquals(map7.getDataList("list2_1"), list2_1);

    DataMap map8 = map6.copy();
    assertTrue(! copyOnWrite || map8.getUnderlying() != map6.getUnderlying());
    assertTrue(! copyOnWrite || map8.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertEquals(map8, map6);
    assertEquals(map8.getDataList("list2_1"), list2_1);

    map7.getDataList("list2_1").remove(0);
    assertTrue(! copyOnWrite || map7.getDataList("list2_1").getUnderlying() != list2_1.getUnderlying());
    assertTrue(! copyOnWrite || map8.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertFalse(map7.getDataList("list2_1").equals(list2_1));
    assertEquals(map8.getDataList("list2_1"), list2_1);

    /* DataList containing DataMap */

    DataList list6 = list1.copy();
    assertTrue(! copyOnWrite || list6.getUnderlying() == list1.getUnderlying());
    assertEquals(list6, list1);
    list6.add(0, map2_1);
    assertTrue(! copyOnWrite || list6.getUnderlying() != list1.getUnderlying());
    assertFalse(list6.equals(list1));

    DataList list7 = list6.copy();
    assertTrue(! copyOnWrite || list7.getUnderlying() != list6.getUnderlying());
    assertTrue(! copyOnWrite || list7.getDataMap(0).getUnderlying() == map2_1.getUnderlying());
    assertEquals(list7, list6);
    assertEquals(list7.getDataMap(0), map2_1);

    DataList list8 = list6.copy();
    assertTrue(! copyOnWrite || list8.getUnderlying() != list6.getUnderlying());
    assertTrue(! copyOnWrite || list8.getDataMap(0).getUnderlying() == map2_1.getUnderlying());
    assertEquals(list8, list6);
    assertEquals(list8.getDataMap(0), map2_1);

    list7.getDataMap(0).remove(RM1_BOOLEAN_KEY);
    assertTrue(! copyOnWrite || list7.getDataMap(0).getUnderlying() != map2_1.getUnderlying());
    assertTrue(! copyOnWrite || list8.getDataMap(0).getUnderlying() == map2_1.getUnderlying());
    assertFalse(list7.getDataMap(0).equals(map2_1));
    assertEquals(list8.getDataMap(0), map2_1);

    /* DataMap containing both DataList and DataMap */

    DataMap map10 = map1.copy();
    assertTrue(! copyOnWrite || map10.getUnderlying() == map1.getUnderlying());
    assertEquals(map10, map1);
    map10.put("map2_1", map2_1);
    map10.put("list2_1", list2_1);
    assertTrue(! copyOnWrite || map10.getUnderlying() != map1.getUnderlying());
    assertFalse(map10.equals(map1));

    DataMap map11 = map10.copy();
    assertTrue(! copyOnWrite || map11.getUnderlying() != map10.getUnderlying());
    assertTrue(! copyOnWrite || map11.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || map11.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertEquals(map11, map10);
    assertEquals(map11.getDataList("list2_1"), list2_1);
    assertEquals(map11.getDataMap("map2_1"), map2_1);

    DataMap map12 = map11.copy();
    assertTrue(! copyOnWrite || map12.getUnderlying() != map11.getUnderlying());
    assertTrue(! copyOnWrite || map12.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || map12.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertEquals(map12, map10);
    assertEquals(map12.getDataList("list2_1"), list2_1);
    assertEquals(map12.getDataMap("map2_1"), map2_1);

    map12.getDataList("list2_1").remove(1);
    assertTrue(! copyOnWrite || map12.getDataList("list2_1").getUnderlying() != list2_1.getUnderlying());
    assertTrue(! copyOnWrite || map11.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertFalse(map12.getDataList("list2_1").equals(list2_1));
    assertEquals(map11.getDataList("list2_1"), list2_1);

    DataMap map13 = map10.copy();
    assertTrue(! copyOnWrite || map13.getUnderlying() != map10.getUnderlying());
    assertTrue(! copyOnWrite || map13.getDataList("list2_1").getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || map13.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertEquals(map13, map10);
    assertEquals(map13.getDataList("list2_1"), list2_1);
    assertEquals(map13.getDataMap("map2_1"), map2_1);

    map13.getDataMap("map2_1").clear();
    assertTrue(! copyOnWrite || map13.getDataMap("map2_1").getUnderlying() != map2_1.getUnderlying());
    assertTrue(! copyOnWrite || map10.getDataMap("map2_1").getUnderlying() == map2_1.getUnderlying());
    assertFalse(map13.getDataMap("map2_1").equals(map2_1));
    assertEquals(map10.getDataMap("map2_1"), map2_1);

    /* DataList containing both DataList and DataMap */

    DataList list10 = list1.copy();
    assertTrue(! copyOnWrite || list10.getUnderlying() == list1.getUnderlying());
    assertEquals(list10, list1);
    list10.add(0, list2_1);
    list10.add(1, map2_1);
    assertTrue(! copyOnWrite || list10.getUnderlying() != list1.getUnderlying());
    assertFalse(list10.equals(list1));

    DataList list11 = list10.copy();
    assertTrue(! copyOnWrite || list11.getUnderlying() != list10.getUnderlying());
    assertTrue(! copyOnWrite || list11.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || list11.getDataMap(1).getUnderlying() == map2_1.getUnderlying());
    assertEquals(list11, list10);
    assertEquals(list11.getDataList(0), list2_1);
    assertEquals(list11.getDataMap(1), map2_1);

    DataList list12 = list11.copy();
    assertTrue(! copyOnWrite || list12.getUnderlying() != list11.getUnderlying());
    assertTrue(! copyOnWrite || list12.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || list12.getDataMap(1).getUnderlying() == map2_1.getUnderlying());
    assertEquals(list12, list11);
    assertEquals(list12.getDataList(0), list2_1);
    assertEquals(list12.getDataMap(1), map2_1);

    list12.getDataList(0).set(2, "xxx");
    assertTrue(! copyOnWrite || list12.getDataList(0).getUnderlying() != list2_1.getUnderlying());
    assertTrue(! copyOnWrite || list11.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertFalse(list12.getDataList(0).equals(list2_1));
    assertEquals(list11.getDataList(0), list2_1);

    DataList list13 = list10.copy();
    assertTrue(! copyOnWrite || list13.getUnderlying() != list10.getUnderlying());
    assertTrue(! copyOnWrite || list13.getDataList(0).getUnderlying() == list2_1.getUnderlying());
    assertTrue(! copyOnWrite || list13.getDataMap(1).getUnderlying() == map2_1.getUnderlying());
    assertEquals(list13, list10);
    assertEquals(list13.getDataList(0), list2_1);
    assertEquals(list13.getDataMap(1), map2_1);

    list13.getDataMap(1).put("x", "XX");
    assertTrue(! copyOnWrite || list13.getDataMap(1).getUnderlying() != map2_1.getUnderlying());
    assertTrue(! copyOnWrite || list10.getDataMap(1).getUnderlying() == map2_1.getUnderlying());
    assertFalse(list13.getDataMap(1).equals(map2_1));
    assertEquals(list10.getDataMap(1), map2_1);

    /* Diamond shaped object graph */

    {
      DataMap a = new DataMap();
      DataList b = new DataList();
      DataList c = new DataList();
      DataMap d = new DataMap();
      a.put("b", b);
      a.put("c", c);
      b.add(d);
      c.add(d);
      DataMap aCopy = a.copy();
      DataList bCopy = (DataList) aCopy.get("b");
      DataList cCopy = (DataList) aCopy.get("c");
      assertSame(bCopy.get(0), cCopy.get(0));
    }
    {
      DataList a = new DataList();
      DataMap b = new DataMap();
      DataMap c = new DataMap();
      DataList d = new DataList();
      a.add(b);
      a.add(c);
      b.put("d", d);
      c.put("d", d);
      DataList aCopy = a.copy();
      DataMap bCopy = (DataMap) aCopy.get(0);
      DataMap cCopy = (DataMap) aCopy.get(1);
      assertSame(bCopy.get("d"), cCopy.get("d"));
    }

    /* Circular object graph */
    {
      // DataList only
      DataList a = new DataList();
      a.disableChecker();
      a.add(a);
      DataList aCopy = a.copy();
      assertSame(aCopy.get(0), aCopy);

      DataList b = new DataList();
      b.disableChecker();
      a.add(b);
      b.add(a);

      aCopy = a.copy();
      DataList bCopy = (DataList) aCopy.get(1);
      assertSame(aCopy.get(0), aCopy);
      assertSame(bCopy.get(0), aCopy);
    }
    /* Circular object graph */
    {
      // DataMap only
      DataMap a = new DataMap();
      a.disableChecker();
      a.put("a", a);
      DataMap aCopy = a.copy();
      assertSame(aCopy.get("a"), aCopy);

      DataMap b = new DataMap();
      b.disableChecker();
      a.put("b", b);
      b.put("a", a);

      aCopy = a.copy();
      DataMap bCopy = (DataMap) aCopy.get("b");
      assertSame(aCopy.get("a"), aCopy);
      assertSame(bCopy.get("a"), aCopy);
    }
  }