1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: BindingTest.java,v 1.1 2008/02/07 17:12:32 mark Exp $
7 */
8
9package com.sleepycat.persist.test;
10
11import static com.sleepycat.persist.model.Relationship.MANY_TO_ONE;
12import static com.sleepycat.persist.model.Relationship.ONE_TO_MANY;
13import static com.sleepycat.persist.model.Relationship.ONE_TO_ONE;
14
15import java.io.File;
16import java.io.IOException;
17import java.lang.reflect.Array;
18import java.lang.reflect.Field;
19import java.math.BigInteger;
20import java.util.ArrayList;
21import java.util.Arrays;
22import java.util.Collection;
23import java.util.Comparator;
24import java.util.Date;
25import java.util.HashMap;
26import java.util.HashSet;
27import java.util.Iterator;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.Locale;
31import java.util.Map;
32import java.util.Set;
33import java.util.TreeMap;
34import java.util.TreeSet;
35
36import junit.framework.TestCase;
37
38import com.sleepycat.bind.EntryBinding;
39import com.sleepycat.compat.DbCompat;
40import com.sleepycat.db.DatabaseConfig;
41import com.sleepycat.db.DatabaseEntry;
42import com.sleepycat.db.DatabaseException;
43import com.sleepycat.db.Environment;
44import com.sleepycat.db.EnvironmentConfig;
45import com.sleepycat.db.ForeignMultiKeyNullifier;
46import com.sleepycat.db.SecondaryKeyCreator;
47import com.sleepycat.db.SecondaryMultiKeyCreator;
48import com.sleepycat.persist.impl.PersistCatalog;
49import com.sleepycat.persist.impl.PersistComparator;
50import com.sleepycat.persist.impl.PersistEntityBinding;
51import com.sleepycat.persist.impl.PersistKeyBinding;
52import com.sleepycat.persist.impl.PersistKeyCreator;
53import com.sleepycat.persist.impl.SimpleCatalog;
54import com.sleepycat.persist.model.AnnotationModel;
55import com.sleepycat.persist.model.ClassMetadata;
56import com.sleepycat.persist.model.Entity;
57import com.sleepycat.persist.model.EntityMetadata;
58import com.sleepycat.persist.model.EntityModel;
59import com.sleepycat.persist.model.KeyField;
60import com.sleepycat.persist.model.Persistent;
61import com.sleepycat.persist.model.PersistentProxy;
62import com.sleepycat.persist.model.PrimaryKey;
63import com.sleepycat.persist.model.PrimaryKeyMetadata;
64import com.sleepycat.persist.model.SecondaryKey;
65import com.sleepycat.persist.model.SecondaryKeyMetadata;
66import com.sleepycat.persist.raw.RawField;
67import com.sleepycat.persist.raw.RawObject;
68import com.sleepycat.persist.raw.RawType;
69import com.sleepycat.util.test.SharedTestUtils;
70import com.sleepycat.util.test.TestEnv;
71
72/**
73 * @author Mark Hayes
74 */
75public class BindingTest extends TestCase {
76
77    private static final String STORE_PREFIX = "persist#foo#";
78
79    private File envHome;
80    private Environment env;
81    private EntityModel model;
82    private PersistCatalog catalog;
83    private DatabaseEntry keyEntry;
84    private DatabaseEntry dataEntry;
85
86    public void setUp()
87        throws IOException {
88
89        envHome = new File(System.getProperty(SharedTestUtils.DEST_DIR));
90        SharedTestUtils.emptyDir(envHome);
91        keyEntry = new DatabaseEntry();
92        dataEntry = new DatabaseEntry();
93    }
94
95    public void tearDown()
96        throws IOException {
97
98        if (env != null) {
99            try {
100                env.close();
101            } catch (DatabaseException e) {
102                System.out.println("During tearDown: " + e);
103            }
104        }
105        envHome = null;
106        env = null;
107        catalog = null;
108        keyEntry = null;
109        dataEntry = null;
110    }
111
112    private void open()
113        throws IOException, DatabaseException {
114
115        EnvironmentConfig envConfig = TestEnv.BDB.getConfig();
116        envConfig.setAllowCreate(true);
117        env = new Environment(envHome, envConfig);
118
119        openCatalog();
120    }
121
122    private void openCatalog()
123        throws DatabaseException {
124
125        model = new AnnotationModel();
126        model.registerClass(LocalizedTextProxy.class);
127        model.registerClass(LocaleProxy.class);
128
129        DatabaseConfig dbConfig = new DatabaseConfig();
130        dbConfig.setAllowCreate(true);
131        DbCompat.setTypeBtree(dbConfig);
132        catalog = new PersistCatalog
133            (null, env, STORE_PREFIX, STORE_PREFIX + "catalog", dbConfig,
134             model, null, false /*rawAccess*/, null /*Store*/);
135    }
136
137    private void close()
138        throws DatabaseException {
139
140        /* Close/open/close catalog to test checks for class evolution. */
141        catalog.close();
142        PersistCatalog.expectNoClassChanges = true;
143        try {
144            openCatalog();
145        } finally {
146            PersistCatalog.expectNoClassChanges = false;
147        }
148        catalog.close();
149        catalog = null;
150
151        env.close();
152        env = null;
153    }
154
155    public void testBasic()
156        throws IOException, DatabaseException {
157
158        open();
159
160        checkEntity(Basic.class,
161                    new Basic(1, "one", 2.2, "three"));
162        checkEntity(Basic.class,
163                    new Basic(0, null, 0, null));
164        checkEntity(Basic.class,
165                    new Basic(-1, "xxx", -2, "xxx"));
166
167        checkMetadata(Basic.class.getName(), new String[][] {
168                          {"id", "long"},
169                          {"one", "java.lang.String"},
170                          {"two", "double"},
171                          {"three", "java.lang.String"},
172                      },
173                      0 /*priKeyIndex*/, null);
174
175        close();
176    }
177
178    @Entity
179    static class Basic implements MyEntity {
180
181        @PrimaryKey
182        private long id;
183        private String one;
184        private double two;
185        private String three;
186
187        private Basic() { }
188
189        private Basic(long id, String one, double two, String three) {
190            this.id = id;
191            this.one = one;
192            this.two = two;
193            this.three = three;
194        }
195
196        public String getBasicOne() {
197            return one;
198        }
199
200        public Object getPriKeyObject() {
201            return id;
202        }
203
204        public void validate(Object other) {
205            Basic o = (Basic) other;
206            TestCase.assertEquals(id, o.id);
207            TestCase.assertTrue(nullOrEqual(one, o.one));
208            TestCase.assertEquals(two, o.two);
209            TestCase.assertTrue(nullOrEqual(three, o.three));
210            if (one == three) {
211                TestCase.assertSame(o.one, o.three);
212            }
213        }
214
215        @Override
216        public String toString() {
217            return "" + id + ' ' + one + ' ' + two;
218        }
219    }
220
221    public void testSimpleTypes()
222        throws IOException, DatabaseException {
223
224        open();
225
226        checkEntity(SimpleTypes.class, new SimpleTypes());
227
228        checkMetadata(SimpleTypes.class.getName(), new String[][] {
229                          {"f0", "boolean"},
230                          {"f1", "char"},
231                          {"f2", "byte"},
232                          {"f3", "short"},
233                          {"f4", "int"},
234                          {"f5", "long"},
235                          {"f6", "float"},
236                          {"f7", "double"},
237                          {"f8", "java.lang.String"},
238                          {"f9", "java.math.BigInteger"},
239                          //{"f10", "java.math.BigDecimal"},
240                          {"f11", "java.util.Date"},
241                          {"f12", "java.lang.Boolean"},
242                          {"f13", "java.lang.Character"},
243                          {"f14", "java.lang.Byte"},
244                          {"f15", "java.lang.Short"},
245                          {"f16", "java.lang.Integer"},
246                          {"f17", "java.lang.Long"},
247                          {"f18", "java.lang.Float"},
248                          {"f19", "java.lang.Double"},
249                      },
250                      0 /*priKeyIndex*/, null);
251
252        close();
253    }
254
255    @Entity
256    static class SimpleTypes implements MyEntity {
257
258        @PrimaryKey
259        private boolean f0 = true;
260        private char f1 = 'a';
261        private byte f2 = 123;
262        private short f3 = 123;
263        private int f4 = 123;
264        private long f5 = 123;
265        private float f6 = 123.4f;
266        private double f7 = 123.4;
267        private String f8 = "xxx";
268        private BigInteger f9 = BigInteger.valueOf(123);
269        //private BigDecimal f10 = BigDecimal.valueOf(123.4);
270        private Date f11 = new Date();
271        private Boolean f12 = true;
272        private Character f13 = 'a';
273        private Byte f14 = 123;
274        private Short f15 = 123;
275        private Integer f16 = 123;
276        private Long f17 = 123L;
277        private Float f18 = 123.4f;
278        private Double f19 = 123.4;
279
280        SimpleTypes() { }
281
282        public Object getPriKeyObject() {
283            return f0;
284        }
285
286        public void validate(Object other) {
287            SimpleTypes o = (SimpleTypes) other;
288            TestCase.assertEquals(f0, o.f0);
289            TestCase.assertEquals(f1, o.f1);
290            TestCase.assertEquals(f2, o.f2);
291            TestCase.assertEquals(f3, o.f3);
292            TestCase.assertEquals(f4, o.f4);
293            TestCase.assertEquals(f5, o.f5);
294            TestCase.assertEquals(f6, o.f6);
295            TestCase.assertEquals(f7, o.f7);
296            TestCase.assertEquals(f8, o.f8);
297            TestCase.assertEquals(f9, o.f9);
298            //TestCase.assertEquals(f10, o.f10);
299            TestCase.assertEquals(f11, o.f11);
300            TestCase.assertEquals(f12, o.f12);
301            TestCase.assertEquals(f13, o.f13);
302            TestCase.assertEquals(f14, o.f14);
303            TestCase.assertEquals(f15, o.f15);
304            TestCase.assertEquals(f16, o.f16);
305            TestCase.assertEquals(f17, o.f17);
306            TestCase.assertEquals(f18, o.f18);
307            TestCase.assertEquals(f19, o.f19);
308        }
309    }
310
311    public void testArrayTypes()
312        throws IOException, DatabaseException {
313
314        open();
315
316        checkEntity(ArrayTypes.class, new ArrayTypes());
317
318        checkMetadata(ArrayTypes.class.getName(), new String[][] {
319                          {"id", "int"},
320                          {"f0", boolean[].class.getName()},
321                          {"f1", char[].class.getName()},
322                          {"f2", byte[].class.getName()},
323                          {"f3", short[].class.getName()},
324                          {"f4", int[].class.getName()},
325                          {"f5", long[].class.getName()},
326                          {"f6", float[].class.getName()},
327                          {"f7", double[].class.getName()},
328                          {"f8", String[].class.getName()},
329                          {"f9", Address[].class.getName()},
330                          {"f10", boolean[][][].class.getName()},
331                          {"f11", String[][][].class.getName()},
332                      },
333                      0 /*priKeyIndex*/, null);
334
335        close();
336    }
337
338    @Entity
339    static class ArrayTypes implements MyEntity {
340
341        @PrimaryKey
342        private int id = 1;
343        private boolean[] f0 =  {false, true};
344        private char[] f1 = {'a', 'b'};
345        private byte[] f2 = {1, 2};
346        private short[] f3 = {1, 2};
347        private int[] f4 = {1, 2};
348        private long[] f5 = {1, 2};
349        private float[] f6 = {1.1f, 2.2f};
350        private double[] f7 = {1.1, 2,2};
351        private String[] f8 = {"xxx", null, "yyy"};
352        private Address[] f9 = {new Address("city", "state", 123),
353                                null,
354                                new Address("x", "y", 444)};
355        private boolean[][][] f10 =
356        {
357            {
358                {false, true},
359                {false, true},
360            },
361            null,
362            {
363                {false, true},
364                {false, true},
365            },
366        };
367        private String[][][] f11 =
368        {
369            {
370                {"xxx", null, "yyy"},
371                null,
372                {"xxx", null, "yyy"},
373            },
374            null,
375            {
376                {"xxx", null, "yyy"},
377                null,
378                {"xxx", null, "yyy"},
379            },
380        };
381
382        ArrayTypes() { }
383
384        public Object getPriKeyObject() {
385            return id;
386        }
387
388        public void validate(Object other) {
389            ArrayTypes o = (ArrayTypes) other;
390            TestCase.assertEquals(id, o.id);
391            TestCase.assertTrue(Arrays.equals(f0, o.f0));
392            TestCase.assertTrue(Arrays.equals(f1, o.f1));
393            TestCase.assertTrue(Arrays.equals(f2, o.f2));
394            TestCase.assertTrue(Arrays.equals(f3, o.f3));
395            TestCase.assertTrue(Arrays.equals(f4, o.f4));
396            TestCase.assertTrue(Arrays.equals(f5, o.f5));
397            TestCase.assertTrue(Arrays.equals(f6, o.f6));
398            TestCase.assertTrue(Arrays.equals(f7, o.f7));
399            TestCase.assertTrue(Arrays.equals(f8, o.f8));
400            TestCase.assertTrue(Arrays.deepEquals(f9, o.f9));
401            TestCase.assertTrue(Arrays.deepEquals(f10, o.f10));
402            TestCase.assertTrue(Arrays.deepEquals(f11, o.f11));
403        }
404    }
405
406    public void testEnumTypes()
407        throws IOException, DatabaseException {
408
409        open();
410
411        checkEntity(EnumTypes.class, new EnumTypes());
412
413        checkMetadata(EnumTypes.class.getName(), new String[][] {
414                          {"f0", "int"},
415                          {"f1", Thread.State.class.getName()},
416                          {"f2", EnumTypes.MyEnum.class.getName()},
417                          {"f3", Object.class.getName()},
418                      },
419                      0 /*priKeyIndex*/, null);
420
421        close();
422    }
423
424    @Entity
425    static class EnumTypes implements MyEntity {
426
427        private static enum MyEnum { ONE, TWO };
428
429        @PrimaryKey
430        private int f0 = 1;
431        private Thread.State f1 = Thread.State.RUNNABLE;
432        private MyEnum f2 = MyEnum.ONE;
433        private Object f3 = MyEnum.TWO;
434
435        EnumTypes() { }
436
437        public Object getPriKeyObject() {
438            return f0;
439        }
440
441        public void validate(Object other) {
442            EnumTypes o = (EnumTypes) other;
443            TestCase.assertEquals(f0, o.f0);
444            TestCase.assertSame(f1, o.f1);
445            TestCase.assertSame(f2, o.f2);
446            TestCase.assertSame(f3, o.f3);
447        }
448    }
449
450    public void testProxyTypes()
451        throws IOException, DatabaseException {
452
453        open();
454
455        checkEntity(ProxyTypes.class, new ProxyTypes());
456
457        checkMetadata(ProxyTypes.class.getName(), new String[][] {
458                          {"f0", "int"},
459                          {"f1", Locale.class.getName()},
460                          {"f2", Set.class.getName()},
461                          {"f3", Set.class.getName()},
462                          {"f4", Object.class.getName()},
463                          {"f5", HashMap.class.getName()},
464                          {"f6", TreeMap.class.getName()},
465                          {"f7", List.class.getName()},
466                          {"f8", LinkedList.class.getName()},
467                          {"f9", LocalizedText.class.getName()},
468                      },
469                      0 /*priKeyIndex*/, null);
470
471        close();
472    }
473
474    @Entity
475    static class ProxyTypes implements MyEntity {
476
477        @PrimaryKey
478        private int f0 = 1;
479        private Locale f1 = Locale.getDefault();
480        private Set<Integer> f2 = new HashSet<Integer>();
481        private Set<Integer> f3 = new TreeSet<Integer>();
482        private Object f4 = new HashSet<Address>();
483        private HashMap<String,Integer> f5 = new HashMap<String,Integer>();
484        private TreeMap<String,Address> f6 = new TreeMap<String,Address>();
485        private List<Integer> f7 = new ArrayList<Integer>();
486        private LinkedList<Integer> f8 = new LinkedList<Integer>();
487        private LocalizedText f9 = new LocalizedText(f1, "xyz");
488
489        ProxyTypes() {
490            f2.add(123);
491            f2.add(456);
492            f3.add(456);
493            f3.add(123);
494            HashSet<Address> s = (HashSet) f4;
495            s.add(new Address("city", "state", 11111));
496            s.add(new Address("city2", "state2", 22222));
497            s.add(new Address("city3", "state3", 33333));
498            f5.put("one", 111);
499            f5.put("two", 222);
500            f5.put("three", 333);
501            f6.put("one", new Address("city", "state", 11111));
502            f6.put("two", new Address("city2", "state2", 22222));
503            f6.put("three", new Address("city3", "state3", 33333));
504            f7.add(123);
505            f7.add(456);
506            f8.add(123);
507            f8.add(456);
508        }
509
510        public Object getPriKeyObject() {
511            return f0;
512        }
513
514        public void validate(Object other) {
515            ProxyTypes o = (ProxyTypes) other;
516            TestCase.assertEquals(f0, o.f0);
517            TestCase.assertEquals(f1, o.f1);
518            TestCase.assertEquals(f2, o.f2);
519            TestCase.assertEquals(f3, o.f3);
520            TestCase.assertEquals(f4, o.f4);
521            TestCase.assertEquals(f5, o.f5);
522            TestCase.assertEquals(f6, o.f6);
523            TestCase.assertEquals(f7, o.f7);
524            TestCase.assertEquals(f8, o.f8);
525            TestCase.assertEquals(f9, o.f9);
526        }
527    }
528
529    @Persistent(proxyFor=Locale.class)
530    static class LocaleProxy implements PersistentProxy<Locale> {
531
532        String language;
533        String country;
534        String variant;
535
536        private LocaleProxy() {}
537
538        public void initializeProxy(Locale object) {
539            language = object.getLanguage();
540            country = object.getCountry();
541            variant = object.getVariant();
542        }
543
544        public Locale convertProxy() {
545            return new Locale(language, country, variant);
546        }
547    }
548
549    static class LocalizedText {
550
551        Locale locale;
552        String text;
553
554        LocalizedText(Locale locale, String text) {
555            this.locale = locale;
556            this.text = text;
557        }
558
559        @Override
560        public boolean equals(Object other) {
561            LocalizedText o = (LocalizedText) other;
562            return text.equals(o.text) &&
563                   locale.equals(o.locale);
564        }
565    }
566
567    @Persistent(proxyFor=LocalizedText.class)
568    static class LocalizedTextProxy implements PersistentProxy<LocalizedText> {
569
570        Locale locale;
571        String text;
572
573        private LocalizedTextProxy() {}
574
575        public void initializeProxy(LocalizedText object) {
576            locale = object.locale;
577            text = object.text;
578        }
579
580        public LocalizedText convertProxy() {
581            return new LocalizedText(locale, text);
582        }
583    }
584
585    public void testEmbedded()
586        throws IOException, DatabaseException {
587
588        open();
589
590        Address a1 = new Address("city", "state", 123);
591        Address a2 = new Address("Wikieup", "AZ", 85360);
592
593        checkEntity(Embedded.class,
594                    new Embedded("x", a1, a2));
595        checkEntity(Embedded.class,
596                    new Embedded("y", a1, null));
597        checkEntity(Embedded.class,
598                    new Embedded("", a2, a2));
599
600        checkMetadata(Embedded.class.getName(), new String[][] {
601                        {"id", "java.lang.String"},
602                        {"idShadow", "java.lang.String"},
603                        {"one", Address.class.getName()},
604                        {"two", Address.class.getName()},
605                      },
606                      0 /*priKeyIndex*/, null);
607
608        checkMetadata(Address.class.getName(), new String[][] {
609                        {"street", "java.lang.String"},
610                        {"city", "java.lang.String"},
611                        {"zip", "int"},
612                      },
613                      -1 /*priKeyIndex*/, null);
614
615        close();
616    }
617
618    @Entity
619    static class Embedded implements MyEntity {
620
621        @PrimaryKey
622        private String id;
623        private String idShadow;
624        private Address one;
625        private Address two;
626
627        private Embedded() { }
628
629        private Embedded(String id, Address one, Address two) {
630            this.id = id;
631            idShadow = id;
632            this.one = one;
633            this.two = two;
634        }
635
636        public Object getPriKeyObject() {
637            return id;
638        }
639
640        public void validate(Object other) {
641            Embedded o = (Embedded) other;
642            TestCase.assertEquals(id, o.id);
643            if (one != null) {
644                one.validate(o.one);
645            } else {
646                assertNull(o.one);
647            }
648            if (two != null) {
649                two.validate(o.two);
650            } else {
651                assertNull(o.two);
652            }
653            TestCase.assertSame(o.id, o.idShadow);
654            if (one == two) {
655                TestCase.assertSame(o.one, o.two);
656            }
657        }
658
659        @Override
660        public String toString() {
661            return "" + id + ' ' + one + ' ' + two;
662        }
663    }
664
665    @Persistent
666    static class Address {
667
668        private String street;
669        private String city;
670        private int zip;
671
672        private Address() {}
673
674        Address(String street, String city, int zip) {
675            this.street = street;
676            this.city = city;
677            this.zip = zip;
678        }
679
680        void validate(Address o) {
681            TestCase.assertTrue(nullOrEqual(street, o.street));
682            TestCase.assertTrue(nullOrEqual(city, o.city));
683            TestCase.assertEquals(zip, o.zip);
684        }
685
686        @Override
687        public String toString() {
688            return "" + street + ' ' + city + ' ' + zip;
689        }
690
691        @Override
692        public boolean equals(Object other) {
693            if (other == null) {
694                return false;
695            }
696            Address o = (Address) other;
697            return nullOrEqual(street, o.street) &&
698                   nullOrEqual(city, o.city) &&
699                   nullOrEqual(zip, o.zip);
700        }
701
702        @Override
703        public int hashCode() {
704            return zip;
705        }
706    }
707
708    public void testSubclass()
709        throws IOException, DatabaseException {
710
711        open();
712
713        checkEntity(Basic.class,
714                    new Subclass(-1, "xxx", -2, "xxx", "xxx", true));
715
716        checkMetadata(Basic.class.getName(), new String[][] {
717                          {"id", "long"},
718                          {"one", "java.lang.String"},
719                          {"two", "double"},
720                          {"three", "java.lang.String"},
721                      },
722                      0 /*priKeyIndex*/, null);
723        checkMetadata(Subclass.class.getName(), new String[][] {
724                          {"one", "java.lang.String"},
725                          {"two", "boolean"},
726                      },
727                      -1 /*priKeyIndex*/, Basic.class.getName());
728
729        close();
730    }
731
732    @Persistent
733    static class Subclass extends Basic {
734
735        private String one;
736        private boolean two;
737
738	private Subclass() {
739	}
740
741        private Subclass(long id, String one, double two, String three,
742                         String subOne, boolean subTwo) {
743            super(id, one, two, three);
744            this.one = subOne;
745            this.two = subTwo;
746	}
747
748        public void validate(Object other) {
749            super.validate(other);
750            Subclass o = (Subclass) other;
751            TestCase.assertTrue(nullOrEqual(one, o.one));
752            TestCase.assertEquals(two, o.two);
753            if (one == getBasicOne()) {
754                TestCase.assertSame(o.one, o.getBasicOne());
755            }
756        }
757    }
758
759    public void testSuperclass()
760        throws IOException, DatabaseException {
761
762        open();
763
764        checkEntity(UseSuperclass.class,
765                    new UseSuperclass(33, "xxx"));
766
767        checkMetadata(Superclass.class.getName(), new String[][] {
768                          {"id", "int"},
769                          {"one", "java.lang.String"},
770                      },
771                      0 /*priKeyIndex*/, null);
772        checkMetadata(UseSuperclass.class.getName(), new String[][] {
773                      },
774                      -1 /*priKeyIndex*/, Superclass.class.getName());
775
776        close();
777    }
778
779    @Persistent
780    static class Superclass implements MyEntity {
781
782        @PrimaryKey
783        private int id;
784        private String one;
785
786        private Superclass() { }
787
788        private Superclass(int id, String one) {
789            this.id = id;
790            this.one = one;
791        }
792
793        public Object getPriKeyObject() {
794            return id;
795        }
796
797        public void validate(Object other) {
798            Superclass o = (Superclass) other;
799            TestCase.assertEquals(id, o.id);
800            TestCase.assertTrue(nullOrEqual(one, o.one));
801        }
802    }
803
804    @Entity
805    static class UseSuperclass extends Superclass {
806
807        private UseSuperclass() { }
808
809        private UseSuperclass(int id, String one) {
810            super(id, one);
811        }
812    }
813
814    public void testAbstract()
815        throws IOException, DatabaseException {
816
817        open();
818
819        checkEntity(EntityUseAbstract.class,
820                    new EntityUseAbstract(33, "xxx"));
821
822        checkMetadata(Abstract.class.getName(), new String[][] {
823                          {"one", "java.lang.String"},
824                      },
825                      -1 /*priKeyIndex*/, null);
826        checkMetadata(EmbeddedUseAbstract.class.getName(), new String[][] {
827                          {"two", "java.lang.String"},
828                      },
829                      -1 /*priKeyIndex*/, Abstract.class.getName());
830        checkMetadata(EntityUseAbstract.class.getName(), new String[][] {
831                          {"id", "int"},
832                          {"f1", EmbeddedUseAbstract.class.getName()},
833                          {"f2", Abstract.class.getName()},
834                          {"f3", Object.class.getName()},
835                          {"f4", Interface.class.getName()},
836                          {"a1", EmbeddedUseAbstract[].class.getName()},
837                          {"a2", Abstract[].class.getName()},
838                          {"a3", Abstract[].class.getName()},
839                          {"a4", Object[].class.getName()},
840                          {"a5", Interface[].class.getName()},
841                          {"a6", Interface[].class.getName()},
842                          {"a7", Interface[].class.getName()},
843                      },
844                      0 /*priKeyIndex*/, Abstract.class.getName());
845
846        close();
847    }
848
849    @Persistent
850    static abstract class Abstract implements Interface {
851
852        String one;
853
854        private Abstract() { }
855
856        private Abstract(String one) {
857            this.one = one;
858        }
859
860        public void validate(Object other) {
861            Abstract o = (Abstract) other;
862            TestCase.assertTrue(nullOrEqual(one, o.one));
863        }
864
865        @Override
866        public boolean equals(Object other) {
867            Abstract o = (Abstract) other;
868            return nullOrEqual(one, o.one);
869        }
870    }
871
872    interface Interface {
873        void validate(Object other);
874    }
875
876    @Persistent
877    static class EmbeddedUseAbstract extends Abstract {
878
879        private String two;
880
881        private EmbeddedUseAbstract() { }
882
883        private EmbeddedUseAbstract(String one, String two) {
884            super(one);
885            this.two = two;
886        }
887
888        @Override
889        public void validate(Object other) {
890            super.validate(other);
891            EmbeddedUseAbstract o = (EmbeddedUseAbstract) other;
892            TestCase.assertTrue(nullOrEqual(two, o.two));
893        }
894
895        @Override
896        public boolean equals(Object other) {
897            if (!super.equals(other)) {
898                return false;
899            }
900            EmbeddedUseAbstract o = (EmbeddedUseAbstract) other;
901            return nullOrEqual(two, o.two);
902        }
903    }
904
905    @Entity
906    static class EntityUseAbstract extends Abstract implements MyEntity {
907
908        @PrimaryKey
909        private int id;
910
911        private EmbeddedUseAbstract f1;
912        private Abstract f2;
913        private Object f3;
914        private Interface f4;
915        private EmbeddedUseAbstract[] a1;
916        private Abstract[] a2;
917        private Abstract[] a3;
918        private Object[] a4;
919        private Interface[] a5;
920        private Interface[] a6;
921        private Interface[] a7;
922
923        private EntityUseAbstract() { }
924
925        private EntityUseAbstract(int id, String one) {
926            super(one);
927            this.id = id;
928            f1 = new EmbeddedUseAbstract(one, one);
929            f2 = new EmbeddedUseAbstract(one + "x", one + "y");
930            f3 = new EmbeddedUseAbstract(null, null);
931            f4 = new EmbeddedUseAbstract(null, null);
932            a1 = new EmbeddedUseAbstract[3];
933            a2 = new EmbeddedUseAbstract[3];
934            a3 = new Abstract[3];
935            a4 = new Object[3];
936            a5 = new EmbeddedUseAbstract[3];
937            a6 = new Abstract[3];
938            a7 = new Interface[3];
939            for (int i = 0; i < 3; i += 1) {
940                a1[i] = new EmbeddedUseAbstract("1" + i, null);
941                a2[i] = new EmbeddedUseAbstract("2" + i, null);
942                a3[i] = new EmbeddedUseAbstract("3" + i, null);
943                a4[i] = new EmbeddedUseAbstract("4" + i, null);
944                a5[i] = new EmbeddedUseAbstract("5" + i, null);
945                a6[i] = new EmbeddedUseAbstract("6" + i, null);
946                a7[i] = new EmbeddedUseAbstract("7" + i, null);
947            }
948        }
949
950        public Object getPriKeyObject() {
951            return id;
952        }
953
954        @Override
955        public void validate(Object other) {
956            super.validate(other);
957            EntityUseAbstract o = (EntityUseAbstract) other;
958            TestCase.assertEquals(id, o.id);
959            f1.validate(o.f1);
960            assertSame(o.one, o.f1.one);
961            assertSame(o.f1.one, o.f1.two);
962            f2.validate(o.f2);
963            ((Abstract) f3).validate(o.f3);
964            f4.validate(o.f4);
965            assertTrue(arrayToString(a1) + ' ' + arrayToString(o.a1),
966                       Arrays.equals(a1, o.a1));
967            assertTrue(Arrays.equals(a2, o.a2));
968            assertTrue(Arrays.equals(a3, o.a3));
969            assertTrue(Arrays.equals(a4, o.a4));
970            assertTrue(Arrays.equals(a5, o.a5));
971            assertTrue(Arrays.equals(a6, o.a6));
972            assertTrue(Arrays.equals(a7, o.a7));
973            assertSame(EmbeddedUseAbstract.class, f2.getClass());
974            assertSame(EmbeddedUseAbstract.class, f3.getClass());
975            assertSame(EmbeddedUseAbstract[].class, a1.getClass());
976            assertSame(EmbeddedUseAbstract[].class, a2.getClass());
977            assertSame(Abstract[].class, a3.getClass());
978            assertSame(Object[].class, a4.getClass());
979            assertSame(EmbeddedUseAbstract[].class, a5.getClass());
980            assertSame(Abstract[].class, a6.getClass());
981            assertSame(Interface[].class, a7.getClass());
982        }
983    }
984
985    public void testCompositeKey()
986        throws IOException, DatabaseException {
987
988        open();
989
990        CompositeKey key =
991            new CompositeKey(123, 456L, "xyz", BigInteger.valueOf(789));
992        checkEntity(UseCompositeKey.class,
993                    new UseCompositeKey(key, "one"));
994
995        checkMetadata(UseCompositeKey.class.getName(), new String[][] {
996                          {"key", CompositeKey.class.getName()},
997                          {"one", "java.lang.String"},
998                      },
999                      0 /*priKeyIndex*/, null);
1000
1001        checkMetadata(CompositeKey.class.getName(), new String[][] {
1002                        {"f1", "int"},
1003                        {"f2", "java.lang.Long"},
1004                        {"f3", "java.lang.String"},
1005                        {"f4", "java.math.BigInteger"},
1006                      },
1007                      -1 /*priKeyIndex*/, null);
1008
1009        close();
1010    }
1011
1012    @Persistent
1013    static class CompositeKey {
1014        @KeyField(3)
1015        private int f1;
1016        @KeyField(2)
1017        private Long f2;
1018        @KeyField(1)
1019        private String f3;
1020        @KeyField(4)
1021        private BigInteger f4;
1022
1023        private CompositeKey() {}
1024
1025        CompositeKey(int f1, Long f2, String f3, BigInteger f4) {
1026            this.f1 = f1;
1027            this.f2 = f2;
1028            this.f3 = f3;
1029            this.f4 = f4;
1030        }
1031
1032        void validate(CompositeKey o) {
1033            TestCase.assertEquals(f1, o.f1);
1034            TestCase.assertTrue(nullOrEqual(f2, o.f2));
1035            TestCase.assertTrue(nullOrEqual(f3, o.f3));
1036            TestCase.assertTrue(nullOrEqual(f4, o.f4));
1037        }
1038
1039        @Override
1040        public boolean equals(Object other) {
1041            CompositeKey o = (CompositeKey) other;
1042            return f1 == o.f1 &&
1043                   nullOrEqual(f2, o.f2) &&
1044                   nullOrEqual(f3, o.f3) &&
1045                   nullOrEqual(f4, o.f4);
1046        }
1047
1048        @Override
1049        public int hashCode() {
1050            return f1;
1051        }
1052
1053        @Override
1054        public String toString() {
1055            return "" + f1 + ' ' + f2 + ' ' + f3 + ' ' + f4;
1056        }
1057    }
1058
1059    @Entity
1060    static class UseCompositeKey implements MyEntity {
1061
1062        @PrimaryKey
1063        private CompositeKey key;
1064        private String one;
1065
1066        private UseCompositeKey() { }
1067
1068        private UseCompositeKey(CompositeKey key, String one) {
1069            this.key = key;
1070            this.one = one;
1071        }
1072
1073        public Object getPriKeyObject() {
1074            return key;
1075        }
1076
1077        public void validate(Object other) {
1078            UseCompositeKey o = (UseCompositeKey) other;
1079            TestCase.assertNotNull(key);
1080            TestCase.assertNotNull(o.key);
1081            key.validate(o.key);
1082            TestCase.assertTrue(nullOrEqual(one, o.one));
1083        }
1084    }
1085
1086    public void testComparableKey()
1087        throws IOException, DatabaseException {
1088
1089        open();
1090
1091        ComparableKey key = new ComparableKey(123, 456);
1092        checkEntity(UseComparableKey.class,
1093                    new UseComparableKey(key, "one"));
1094
1095        checkMetadata(UseComparableKey.class.getName(), new String[][] {
1096                          {"key", ComparableKey.class.getName()},
1097                          {"one", "java.lang.String"},
1098                      },
1099                      0 /*priKeyIndex*/, null);
1100
1101        checkMetadata(ComparableKey.class.getName(), new String[][] {
1102                        {"f1", "int"},
1103                        {"f2", "int"},
1104                      },
1105                      -1 /*priKeyIndex*/, null);
1106
1107        ClassMetadata classMeta =
1108            model.getClassMetadata(UseComparableKey.class.getName());
1109        assertNotNull(classMeta);
1110
1111        PersistKeyBinding binding = new PersistKeyBinding
1112            (catalog, ComparableKey.class.getName(), false);
1113
1114        PersistComparator comparator = new PersistComparator
1115            (ComparableKey.class.getName(),
1116             classMeta.getCompositeKeyFields(),
1117             binding);
1118
1119        compareKeys(comparator, binding, new ComparableKey(1, 1),
1120                                         new ComparableKey(1, 1), 0);
1121        compareKeys(comparator, binding, new ComparableKey(1, 2),
1122                                         new ComparableKey(1, 1), -1);
1123        compareKeys(comparator, binding, new ComparableKey(2, 1),
1124                                         new ComparableKey(1, 1), -1);
1125        compareKeys(comparator, binding, new ComparableKey(2, 1),
1126                                         new ComparableKey(3, 1), 1);
1127
1128        close();
1129    }
1130
1131    private void compareKeys(Comparator<Object> comparator,
1132                             EntryBinding binding,
1133                             Object key1,
1134                             Object key2,
1135                             int expectResult) {
1136        DatabaseEntry entry1 = new DatabaseEntry();
1137        DatabaseEntry entry2 = new DatabaseEntry();
1138        binding.objectToEntry(key1, entry1);
1139        binding.objectToEntry(key2, entry2);
1140        int result = comparator.compare(entry1.getData(), entry2.getData());
1141        assertEquals(expectResult, result);
1142    }
1143
1144    @Persistent
1145    static class ComparableKey implements Comparable<ComparableKey> {
1146        @KeyField(2)
1147        private int f1;
1148        @KeyField(1)
1149        private int f2;
1150
1151        private ComparableKey() {}
1152
1153        ComparableKey(int f1, int f2) {
1154            this.f1 = f1;
1155            this.f2 = f2;
1156        }
1157
1158        void validate(ComparableKey o) {
1159            TestCase.assertEquals(f1, o.f1);
1160            TestCase.assertEquals(f2, o.f2);
1161        }
1162
1163        @Override
1164        public boolean equals(Object other) {
1165            ComparableKey o = (ComparableKey) other;
1166            return f1 == o.f1 && f2 == o.f2;
1167        }
1168
1169        @Override
1170        public int hashCode() {
1171            return f1 + f2;
1172        }
1173
1174        @Override
1175        public String toString() {
1176            return "" + f1 + ' ' + f2;
1177        }
1178
1179        /** Compare f1 then f2, in reverse integer order. */
1180        public int compareTo(ComparableKey o) {
1181            if (f1 != o.f1) {
1182                return o.f1 - f1;
1183            } else {
1184                return o.f2 - f2;
1185            }
1186        }
1187    }
1188
1189    @Entity
1190    static class UseComparableKey implements MyEntity {
1191
1192        @PrimaryKey
1193        private ComparableKey key;
1194        private String one;
1195
1196        private UseComparableKey() { }
1197
1198        private UseComparableKey(ComparableKey key, String one) {
1199            this.key = key;
1200            this.one = one;
1201        }
1202
1203        public Object getPriKeyObject() {
1204            return key;
1205        }
1206
1207        public void validate(Object other) {
1208            UseComparableKey o = (UseComparableKey) other;
1209            TestCase.assertNotNull(key);
1210            TestCase.assertNotNull(o.key);
1211            key.validate(o.key);
1212            TestCase.assertTrue(nullOrEqual(one, o.one));
1213        }
1214    }
1215
1216    public void testSecKeys()
1217        throws IOException, DatabaseException {
1218
1219        open();
1220
1221        SecKeys obj = new SecKeys();
1222        checkEntity(SecKeys.class, obj);
1223
1224        checkMetadata(SecKeys.class.getName(), new String[][] {
1225                          {"id", "long"},
1226                          {"f0", "boolean"},
1227                          {"g0", "boolean"},
1228                          {"f1", "char"},
1229                          {"g1", "char"},
1230                          {"f2", "byte"},
1231                          {"g2", "byte"},
1232                          {"f3", "short"},
1233                          {"g3", "short"},
1234                          {"f4", "int"},
1235                          {"g4", "int"},
1236                          {"f5", "long"},
1237                          {"g5", "long"},
1238                          {"f6", "float"},
1239                          {"g6", "float"},
1240                          {"f7", "double"},
1241                          {"g7", "double"},
1242                          {"f8", "java.lang.String"},
1243                          {"g8", "java.lang.String"},
1244                          {"f9", "java.math.BigInteger"},
1245                          {"g9", "java.math.BigInteger"},
1246                          //{"f10", "java.math.BigDecimal"},
1247                          //{"g10", "java.math.BigDecimal"},
1248                          {"f11", "java.util.Date"},
1249                          {"g11", "java.util.Date"},
1250                          {"f12", "java.lang.Boolean"},
1251                          {"g12", "java.lang.Boolean"},
1252                          {"f13", "java.lang.Character"},
1253                          {"g13", "java.lang.Character"},
1254                          {"f14", "java.lang.Byte"},
1255                          {"g14", "java.lang.Byte"},
1256                          {"f15", "java.lang.Short"},
1257                          {"g15", "java.lang.Short"},
1258                          {"f16", "java.lang.Integer"},
1259                          {"g16", "java.lang.Integer"},
1260                          {"f17", "java.lang.Long"},
1261                          {"g17", "java.lang.Long"},
1262                          {"f18", "java.lang.Float"},
1263                          {"g18", "java.lang.Float"},
1264                          {"f19", "java.lang.Double"},
1265                          {"g19", "java.lang.Double"},
1266                          {"f20", CompositeKey.class.getName()},
1267                          {"g20", CompositeKey.class.getName()},
1268                          {"f21", int[].class.getName()},
1269                          {"g21", int[].class.getName()},
1270                          {"f22", Integer[].class.getName()},
1271                          {"g22", Integer[].class.getName()},
1272                          {"f23", Set.class.getName()},
1273                          {"g23", Set.class.getName()},
1274                          {"f24", CompositeKey[].class.getName()},
1275                          {"g24", CompositeKey[].class.getName()},
1276                          {"f25", Set.class.getName()},
1277                          {"g25", Set.class.getName()},
1278                          {"f31", "java.util.Date"},
1279                          {"f32", "java.lang.Boolean"},
1280                          {"f33", "java.lang.Character"},
1281                          {"f34", "java.lang.Byte"},
1282                          {"f35", "java.lang.Short"},
1283                          {"f36", "java.lang.Integer"},
1284                          {"f37", "java.lang.Long"},
1285                          {"f38", "java.lang.Float"},
1286                          {"f39", "java.lang.Double"},
1287                          {"f40", CompositeKey.class.getName()},
1288                      },
1289                      0 /*priKeyIndex*/, null);
1290
1291        checkSecKey(obj, "f0", obj.f0, Boolean.class);
1292        checkSecKey(obj, "f1", obj.f1, Character.class);
1293        checkSecKey(obj, "f2", obj.f2, Byte.class);
1294        checkSecKey(obj, "f3", obj.f3, Short.class);
1295        checkSecKey(obj, "f4", obj.f4, Integer.class);
1296        checkSecKey(obj, "f5", obj.f5, Long.class);
1297        checkSecKey(obj, "f6", obj.f6, Float.class);
1298        checkSecKey(obj, "f7", obj.f7, Double.class);
1299        checkSecKey(obj, "f8", obj.f8, String.class);
1300        checkSecKey(obj, "f9", obj.f9, BigInteger.class);
1301        //checkSecKey(obj, "f10", obj.f10, BigDecimal.class);
1302        checkSecKey(obj, "f11", obj.f11, Date.class);
1303        checkSecKey(obj, "f12", obj.f12, Boolean.class);
1304        checkSecKey(obj, "f13", obj.f13, Character.class);
1305        checkSecKey(obj, "f14", obj.f14, Byte.class);
1306        checkSecKey(obj, "f15", obj.f15, Short.class);
1307        checkSecKey(obj, "f16", obj.f16, Integer.class);
1308        checkSecKey(obj, "f17", obj.f17, Long.class);
1309        checkSecKey(obj, "f18", obj.f18, Float.class);
1310        checkSecKey(obj, "f19", obj.f19, Double.class);
1311        checkSecKey(obj, "f20", obj.f20, CompositeKey.class);
1312
1313        checkSecMultiKey(obj, "f21", toSet(obj.f21), Integer.class);
1314        checkSecMultiKey(obj, "f22", toSet(obj.f22), Integer.class);
1315        checkSecMultiKey(obj, "f23", toSet(obj.f23), Integer.class);
1316        checkSecMultiKey(obj, "f24", toSet(obj.f24), CompositeKey.class);
1317        checkSecMultiKey(obj, "f25", toSet(obj.f25), CompositeKey.class);
1318
1319        nullifySecKey(obj, "f8", obj.f8, String.class);
1320        nullifySecKey(obj, "f9", obj.f9, BigInteger.class);
1321        //nullifySecKey(obj, "f10", obj.f10, BigDecimal.class);
1322        nullifySecKey(obj, "f11", obj.f11, Date.class);
1323        nullifySecKey(obj, "f12", obj.f12, Boolean.class);
1324        nullifySecKey(obj, "f13", obj.f13, Character.class);
1325        nullifySecKey(obj, "f14", obj.f14, Byte.class);
1326        nullifySecKey(obj, "f15", obj.f15, Short.class);
1327        nullifySecKey(obj, "f16", obj.f16, Integer.class);
1328        nullifySecKey(obj, "f17", obj.f17, Long.class);
1329        nullifySecKey(obj, "f18", obj.f18, Float.class);
1330        nullifySecKey(obj, "f19", obj.f19, Double.class);
1331        nullifySecKey(obj, "f20", obj.f20, CompositeKey.class);
1332
1333        nullifySecMultiKey(obj, "f21", obj.f21, Integer.class);
1334        nullifySecMultiKey(obj, "f22", obj.f22, Integer.class);
1335        nullifySecMultiKey(obj, "f23", obj.f23, Integer.class);
1336        nullifySecMultiKey(obj, "f24", obj.f24, CompositeKey.class);
1337        nullifySecMultiKey(obj, "f25", obj.f25, CompositeKey.class);
1338
1339        nullifySecKey(obj, "f31", obj.f31, Date.class);
1340        nullifySecKey(obj, "f32", obj.f32, Boolean.class);
1341        nullifySecKey(obj, "f33", obj.f33, Character.class);
1342        nullifySecKey(obj, "f34", obj.f34, Byte.class);
1343        nullifySecKey(obj, "f35", obj.f35, Short.class);
1344        nullifySecKey(obj, "f36", obj.f36, Integer.class);
1345        nullifySecKey(obj, "f37", obj.f37, Long.class);
1346        nullifySecKey(obj, "f38", obj.f38, Float.class);
1347        nullifySecKey(obj, "f39", obj.f39, Double.class);
1348        nullifySecKey(obj, "f40", obj.f40, CompositeKey.class);
1349
1350        close();
1351    }
1352
1353    static Set toSet(int[] a) {
1354        Set set = new HashSet();
1355        for (int i : a) {
1356            set.add(i);
1357        }
1358        return set;
1359    }
1360
1361    static Set toSet(Object[] a) {
1362        return new HashSet(Arrays.asList(a));
1363    }
1364
1365    static Set toSet(Set s) {
1366        return s;
1367    }
1368
1369    @Entity
1370    static class SecKeys implements MyEntity {
1371
1372        @PrimaryKey
1373        long id;
1374
1375        @SecondaryKey(relate=MANY_TO_ONE)
1376        private boolean f0 = false;
1377        private boolean g0 = false;
1378
1379        @SecondaryKey(relate=MANY_TO_ONE)
1380        private char f1 = '1';
1381        private char g1 = '1';
1382
1383        @SecondaryKey(relate=MANY_TO_ONE)
1384        private byte f2 = 2;
1385        private byte g2 = 2;
1386
1387        @SecondaryKey(relate=MANY_TO_ONE)
1388        private short f3 = 3;
1389        private short g3 = 3;
1390
1391        @SecondaryKey(relate=MANY_TO_ONE)
1392        private int f4 = 4;
1393        private int g4 = 4;
1394
1395        @SecondaryKey(relate=MANY_TO_ONE)
1396        private long f5 = 5;
1397        private long g5 = 5;
1398
1399        @SecondaryKey(relate=MANY_TO_ONE)
1400        private float f6 = 6.6f;
1401        private float g6 = 6.6f;
1402
1403        @SecondaryKey(relate=MANY_TO_ONE)
1404        private double f7 = 7.7;
1405        private double g7 = 7.7;
1406
1407        @SecondaryKey(relate=MANY_TO_ONE)
1408        private String f8 = "8";
1409        private String g8 = "8";
1410
1411        @SecondaryKey(relate=MANY_TO_ONE)
1412        private BigInteger f9;
1413        private BigInteger g9;
1414
1415        //@SecondaryKey(relate=MANY_TO_ONE)
1416        //private BigDecimal f10;
1417        //private BigDecimal g10;
1418
1419        @SecondaryKey(relate=MANY_TO_ONE)
1420        private Date f11 = new Date(11);
1421        private Date g11 = new Date(11);
1422
1423        @SecondaryKey(relate=MANY_TO_ONE)
1424        private Boolean f12 = true;
1425        private Boolean g12 = true;
1426
1427        @SecondaryKey(relate=MANY_TO_ONE)
1428        private Character f13 = '3';
1429        private Character g13 = '3';
1430
1431        @SecondaryKey(relate=MANY_TO_ONE)
1432        private Byte f14 = 14;
1433        private Byte g14 = 14;
1434
1435        @SecondaryKey(relate=MANY_TO_ONE)
1436        private Short f15 = 15;
1437        private Short g15 = 15;
1438
1439        @SecondaryKey(relate=MANY_TO_ONE)
1440        private Integer f16 = 16;
1441        private Integer g16 = 16;
1442
1443        @SecondaryKey(relate=MANY_TO_ONE)
1444        private Long f17= 17L;
1445        private Long g17= 17L;
1446
1447        @SecondaryKey(relate=MANY_TO_ONE)
1448        private Float f18 = 18.18f;
1449        private Float g18 = 18.18f;
1450
1451        @SecondaryKey(relate=MANY_TO_ONE)
1452        private Double f19 = 19.19;
1453        private Double g19 = 19.19;
1454
1455        @SecondaryKey(relate=MANY_TO_ONE)
1456        private CompositeKey f20 =
1457            new CompositeKey(20, 20L, "20", BigInteger.valueOf(20));
1458        private CompositeKey g20 =
1459            new CompositeKey(20, 20L, "20", BigInteger.valueOf(20));
1460
1461        private static int[] arrayOfInt = { 100, 101, 102 };
1462
1463        private static Integer[] arrayOfInteger = { 100, 101, 102 };
1464
1465        private static CompositeKey[] arrayOfCompositeKey = {
1466            new CompositeKey(100, 100L, "100", BigInteger.valueOf(100)),
1467            new CompositeKey(101, 101L, "101", BigInteger.valueOf(101)),
1468            new CompositeKey(102, 102L, "102", BigInteger.valueOf(102)),
1469        };
1470
1471        @SecondaryKey(relate=ONE_TO_MANY)
1472        private int[] f21 = arrayOfInt;
1473        private int[] g21 = f21;
1474
1475        @SecondaryKey(relate=ONE_TO_MANY)
1476        private Integer[] f22 = arrayOfInteger;
1477        private Integer[] g22 = f22;
1478
1479        @SecondaryKey(relate=ONE_TO_MANY)
1480        private Set<Integer> f23 = toSet(arrayOfInteger);
1481        private Set<Integer> g23 = f23;
1482
1483        @SecondaryKey(relate=ONE_TO_MANY)
1484        private CompositeKey[] f24 = arrayOfCompositeKey;
1485        private CompositeKey[] g24 = f24;
1486
1487        @SecondaryKey(relate=ONE_TO_MANY)
1488        private Set<CompositeKey> f25 = toSet(arrayOfCompositeKey);
1489        private Set<CompositeKey> g25 = f25;
1490
1491        /* Repeated key values to test shared references. */
1492
1493        @SecondaryKey(relate=MANY_TO_ONE)
1494        private Date f31 = f11;
1495
1496        @SecondaryKey(relate=MANY_TO_ONE)
1497        private Boolean f32 = f12;
1498
1499        @SecondaryKey(relate=MANY_TO_ONE)
1500        private Character f33 = f13;
1501
1502        @SecondaryKey(relate=MANY_TO_ONE)
1503        private Byte f34 = f14;
1504
1505        @SecondaryKey(relate=MANY_TO_ONE)
1506        private Short f35 = f15;
1507
1508        @SecondaryKey(relate=MANY_TO_ONE)
1509        private Integer f36 = f16;
1510
1511        @SecondaryKey(relate=MANY_TO_ONE)
1512        private Long f37= f17;
1513
1514        @SecondaryKey(relate=MANY_TO_ONE)
1515        private Float f38 = f18;
1516
1517        @SecondaryKey(relate=MANY_TO_ONE)
1518        private Double f39 = f19;
1519
1520        @SecondaryKey(relate=MANY_TO_ONE)
1521        private CompositeKey f40 = f20;
1522
1523        public Object getPriKeyObject() {
1524            return id;
1525        }
1526
1527        public void validate(Object other) {
1528            SecKeys o = (SecKeys) other;
1529            TestCase.assertEquals(id, o.id);
1530
1531            TestCase.assertEquals(f0, o.f0);
1532            TestCase.assertEquals(f1, o.f1);
1533            TestCase.assertEquals(f2, o.f2);
1534            TestCase.assertEquals(f3, o.f3);
1535            TestCase.assertEquals(f4, o.f4);
1536            TestCase.assertEquals(f5, o.f5);
1537            TestCase.assertEquals(f6, o.f6);
1538            TestCase.assertEquals(f7, o.f7);
1539            TestCase.assertEquals(f8, o.f8);
1540            TestCase.assertEquals(f9, o.f9);
1541            //TestCase.assertEquals(f10, o.f10);
1542            TestCase.assertEquals(f11, o.f11);
1543            TestCase.assertEquals(f12, o.f12);
1544            TestCase.assertEquals(f13, o.f13);
1545            TestCase.assertEquals(f14, o.f14);
1546            TestCase.assertEquals(f15, o.f15);
1547            TestCase.assertEquals(f16, o.f16);
1548            TestCase.assertEquals(f17, o.f17);
1549            TestCase.assertEquals(f18, o.f18);
1550            TestCase.assertEquals(f19, o.f19);
1551            TestCase.assertEquals(f20, o.f20);
1552            TestCase.assertTrue(Arrays.equals(f21, o.f21));
1553            TestCase.assertTrue(Arrays.equals(f22, o.f22));
1554            TestCase.assertEquals(f23, o.f23);
1555            TestCase.assertTrue(Arrays.equals(f24, o.f24));
1556            TestCase.assertEquals(f25, o.f25);
1557
1558            TestCase.assertEquals(g0, o.g0);
1559            TestCase.assertEquals(g1, o.g1);
1560            TestCase.assertEquals(g2, o.g2);
1561            TestCase.assertEquals(g3, o.g3);
1562            TestCase.assertEquals(g4, o.g4);
1563            TestCase.assertEquals(g5, o.g5);
1564            TestCase.assertEquals(g6, o.g6);
1565            TestCase.assertEquals(g7, o.g7);
1566            TestCase.assertEquals(g8, o.g8);
1567            TestCase.assertEquals(g9, o.g9);
1568            //TestCase.assertEquals(g10, o.g10);
1569            TestCase.assertEquals(g11, o.g11);
1570            TestCase.assertEquals(g12, o.g12);
1571            TestCase.assertEquals(g13, o.g13);
1572            TestCase.assertEquals(g14, o.g14);
1573            TestCase.assertEquals(g15, o.g15);
1574            TestCase.assertEquals(g16, o.g16);
1575            TestCase.assertEquals(g17, o.g17);
1576            TestCase.assertEquals(g18, o.g18);
1577            TestCase.assertEquals(g19, o.g19);
1578            TestCase.assertEquals(g20, o.g20);
1579            TestCase.assertTrue(Arrays.equals(g21, o.g21));
1580            TestCase.assertTrue(Arrays.equals(g22, o.g22));
1581            TestCase.assertEquals(g23, o.g23);
1582            TestCase.assertTrue(Arrays.equals(g24, o.g24));
1583            TestCase.assertEquals(g25, o.g25);
1584
1585            TestCase.assertEquals(f31, o.f31);
1586            TestCase.assertEquals(f32, o.f32);
1587            TestCase.assertEquals(f33, o.f33);
1588            TestCase.assertEquals(f34, o.f34);
1589            TestCase.assertEquals(f35, o.f35);
1590            TestCase.assertEquals(f36, o.f36);
1591            TestCase.assertEquals(f37, o.f37);
1592            TestCase.assertEquals(f38, o.f38);
1593            TestCase.assertEquals(f39, o.f39);
1594            TestCase.assertEquals(f40, o.f40);
1595
1596            checkSameIfNonNull(o.f31, o.f11);
1597            checkSameIfNonNull(o.f32, o.f12);
1598            checkSameIfNonNull(o.f33, o.f13);
1599            checkSameIfNonNull(o.f34, o.f14);
1600            checkSameIfNonNull(o.f35, o.f15);
1601            checkSameIfNonNull(o.f36, o.f16);
1602            checkSameIfNonNull(o.f37, o.f17);
1603            checkSameIfNonNull(o.f38, o.f18);
1604            checkSameIfNonNull(o.f39, o.f19);
1605            checkSameIfNonNull(o.f40, o.f20);
1606        }
1607    }
1608
1609    public void testSecKeyRefToPriKey()
1610        throws IOException, DatabaseException {
1611
1612        open();
1613
1614        SecKeyRefToPriKey obj = new SecKeyRefToPriKey();
1615        checkEntity(SecKeyRefToPriKey.class, obj);
1616
1617        checkMetadata(SecKeyRefToPriKey.class.getName(), new String[][] {
1618                          {"priKey", "java.lang.String"},
1619                          {"secKey1", "java.lang.String"},
1620                          {"secKey2", String[].class.getName()},
1621                          {"secKey3", Set.class.getName()},
1622                      },
1623                      0 /*priKeyIndex*/, null);
1624
1625        checkSecKey(obj, "secKey1", obj.secKey1, String.class);
1626        checkSecMultiKey(obj, "secKey2", toSet(obj.secKey2), String.class);
1627        checkSecMultiKey(obj, "secKey3", toSet(obj.secKey3), String.class);
1628
1629        close();
1630    }
1631
1632    @Entity
1633    static class SecKeyRefToPriKey implements MyEntity {
1634
1635        @PrimaryKey
1636        private String priKey;
1637
1638        @SecondaryKey(relate=ONE_TO_ONE)
1639        private String secKey1;
1640
1641        @SecondaryKey(relate=ONE_TO_MANY)
1642        private String[] secKey2;
1643
1644        @SecondaryKey(relate=ONE_TO_MANY)
1645        private Set<String> secKey3 = new HashSet<String>();
1646
1647        private SecKeyRefToPriKey() {
1648            priKey = "sharedValue";
1649            secKey1 = priKey;
1650            secKey2 = new String[] { priKey };
1651            secKey3.add(priKey);
1652        }
1653
1654        public Object getPriKeyObject() {
1655            return priKey;
1656        }
1657
1658        public void validate(Object other) {
1659            SecKeyRefToPriKey o = (SecKeyRefToPriKey) other;
1660            TestCase.assertEquals(priKey, o.priKey);
1661            TestCase.assertNotNull(o.secKey1);
1662            TestCase.assertEquals(1, o.secKey2.length);
1663            TestCase.assertEquals(1, o.secKey3.size());
1664            TestCase.assertSame(o.secKey1, o.priKey);
1665            TestCase.assertSame(o.secKey2[0], o.priKey);
1666            TestCase.assertSame(o.secKey3.iterator().next(), o.priKey);
1667        }
1668    }
1669
1670    public void testSecKeyInSuperclass()
1671        throws IOException, DatabaseException {
1672
1673        open();
1674
1675        SecKeyInSuperclassEntity obj = new SecKeyInSuperclassEntity();
1676        checkEntity(SecKeyInSuperclassEntity.class, obj);
1677
1678        checkMetadata(SecKeyInSuperclass.class.getName(), new String[][] {
1679                          {"priKey", "java.lang.String"},
1680                          {"secKey1", String.class.getName()},
1681                      },
1682                      0/*priKeyIndex*/, null);
1683
1684        checkMetadata(SecKeyInSuperclassEntity.class.getName(), new String[][] {
1685                          {"secKey2", "java.lang.String"},
1686                      },
1687                      -1 /*priKeyIndex*/, SecKeyInSuperclass.class.getName());
1688
1689        checkSecKey
1690            (obj, SecKeyInSuperclassEntity.class, "secKey1", obj.secKey1,
1691             String.class);
1692        checkSecKey
1693            (obj, SecKeyInSuperclassEntity.class, "secKey2", obj.secKey2,
1694             String.class);
1695
1696        close();
1697    }
1698
1699    @Persistent
1700    static class SecKeyInSuperclass implements MyEntity {
1701
1702        @PrimaryKey
1703        String priKey = "1";
1704
1705        @SecondaryKey(relate=ONE_TO_ONE)
1706        String secKey1 = "1";
1707
1708        public Object getPriKeyObject() {
1709            return priKey;
1710        }
1711
1712        public void validate(Object other) {
1713            SecKeyInSuperclass o = (SecKeyInSuperclass) other;
1714            TestCase.assertEquals(secKey1, o.secKey1);
1715        }
1716    }
1717
1718    @Entity
1719    static class SecKeyInSuperclassEntity extends SecKeyInSuperclass {
1720
1721        @SecondaryKey(relate=ONE_TO_ONE)
1722        String secKey2 = "2";
1723
1724        public void validate(Object other) {
1725            super.validate(other);
1726            SecKeyInSuperclassEntity o = (SecKeyInSuperclassEntity) other;
1727            TestCase.assertEquals(priKey, o.priKey);
1728            TestCase.assertEquals(secKey2, o.secKey2);
1729        }
1730    }
1731
1732    public void testSecKeyInSubclass()
1733        throws IOException, DatabaseException {
1734
1735        open();
1736
1737        SecKeyInSubclass obj = new SecKeyInSubclass();
1738        checkEntity(SecKeyInSubclassEntity.class, obj);
1739
1740        checkMetadata(SecKeyInSubclassEntity.class.getName(), new String[][] {
1741                          {"priKey", "java.lang.String"},
1742                          {"secKey1", "java.lang.String"},
1743                      },
1744                      0 /*priKeyIndex*/, null);
1745
1746        checkMetadata(SecKeyInSubclass.class.getName(), new String[][] {
1747                          {"secKey2", String.class.getName()},
1748                      },
1749                      -1 /*priKeyIndex*/,
1750                      SecKeyInSubclassEntity.class.getName());
1751
1752        checkSecKey
1753            (obj, SecKeyInSubclassEntity.class, "secKey1", obj.secKey1,
1754             String.class);
1755        checkSecKey
1756            (obj, SecKeyInSubclassEntity.class, "secKey2", obj.secKey2,
1757             String.class);
1758
1759        close();
1760    }
1761
1762    @Entity
1763    static class SecKeyInSubclassEntity implements MyEntity {
1764
1765        @PrimaryKey
1766        String priKey = "1";
1767
1768        @SecondaryKey(relate=ONE_TO_ONE)
1769        String secKey1;
1770
1771        public Object getPriKeyObject() {
1772            return priKey;
1773        }
1774
1775        public void validate(Object other) {
1776            SecKeyInSubclassEntity o = (SecKeyInSubclassEntity) other;
1777            TestCase.assertEquals(priKey, o.priKey);
1778            TestCase.assertEquals(secKey1, o.secKey1);
1779        }
1780    }
1781
1782    @Persistent
1783    static class SecKeyInSubclass extends SecKeyInSubclassEntity {
1784
1785        @SecondaryKey(relate=ONE_TO_ONE)
1786        String secKey2 = "2";
1787
1788        public void validate(Object other) {
1789            super.validate(other);
1790            SecKeyInSubclass o = (SecKeyInSubclass) other;
1791            TestCase.assertEquals(secKey2, o.secKey2);
1792        }
1793    }
1794
1795    private static void checkSameIfNonNull(Object o1, Object o2) {
1796        if (o1 != null && o2 != null) {
1797            assertSame(o1, o2);
1798        }
1799    }
1800
1801    private void checkEntity(Class entityCls, MyEntity entity)
1802        throws DatabaseException {
1803
1804        Object priKey = entity.getPriKeyObject();
1805        Class keyCls = priKey.getClass();
1806        DatabaseEntry keyEntry2 = new DatabaseEntry();
1807        DatabaseEntry dataEntry2 = new DatabaseEntry();
1808
1809        /* Write object, read it back and validate (compare) it. */
1810        PersistEntityBinding entityBinding =
1811            new PersistEntityBinding(catalog, entityCls.getName(), false);
1812        entityBinding.objectToData(entity, dataEntry);
1813        entityBinding.objectToKey(entity, keyEntry);
1814        Object entity2 = entityBinding.entryToObject(keyEntry, dataEntry);
1815        entity.validate(entity2);
1816
1817        /* Read back the primary key and validate it. */
1818        PersistKeyBinding keyBinding =
1819            new PersistKeyBinding(catalog, keyCls.getName(), false);
1820        Object priKey2 = keyBinding.entryToObject(keyEntry);
1821        assertEquals(priKey, priKey2);
1822        keyBinding.objectToEntry(priKey2, keyEntry2);
1823        assertEquals(keyEntry, keyEntry2);
1824
1825        /* Check raw entity binding. */
1826        PersistEntityBinding rawEntityBinding =
1827            new PersistEntityBinding(catalog, entityCls.getName(), true);
1828        RawObject rawEntity =
1829            (RawObject) rawEntityBinding.entryToObject(keyEntry, dataEntry);
1830        rawEntityBinding.objectToKey(rawEntity, keyEntry2);
1831        rawEntityBinding.objectToData(rawEntity, dataEntry2);
1832        entity2 = entityBinding.entryToObject(keyEntry2, dataEntry2);
1833        entity.validate(entity2);
1834        RawObject rawEntity2 =
1835            (RawObject) rawEntityBinding.entryToObject(keyEntry2, dataEntry2);
1836        assertEquals(rawEntity, rawEntity2);
1837        assertEquals(dataEntry, dataEntry2);
1838        assertEquals(keyEntry, keyEntry2);
1839
1840        /* Check that raw entity can be converted to a regular entity. */
1841        entity2 = catalog.convertRawObject(rawEntity, null);
1842        entity.validate(entity2);
1843
1844        /* Check raw key binding. */
1845        PersistKeyBinding rawKeyBinding =
1846            new PersistKeyBinding(catalog, keyCls.getName(), true);
1847        Object rawKey = rawKeyBinding.entryToObject(keyEntry);
1848        rawKeyBinding.objectToEntry(rawKey, keyEntry2);
1849        priKey2 = keyBinding.entryToObject(keyEntry2);
1850        assertEquals(priKey, priKey2);
1851        assertEquals(keyEntry, keyEntry2);
1852    }
1853
1854    private void checkSecKey(MyEntity entity,
1855                             String keyName,
1856                             Object keyValue,
1857                             Class keyCls)
1858        throws DatabaseException {
1859
1860        checkSecKey(entity, entity.getClass(), keyName, keyValue, keyCls);
1861    }
1862
1863    private void checkSecKey(MyEntity entity,
1864                             Class entityCls,
1865                             String keyName,
1866                             Object keyValue,
1867                             Class keyCls)
1868        throws DatabaseException {
1869
1870        /* Get entity metadata. */
1871        EntityMetadata entityMeta =
1872            model.getEntityMetadata(entityCls.getName());
1873        assertNotNull(entityMeta);
1874
1875        /* Get secondary key metadata. */
1876        SecondaryKeyMetadata secKeyMeta =
1877            entityMeta.getSecondaryKeys().get(keyName);
1878        assertNotNull(secKeyMeta);
1879
1880        /* Create key creator/nullifier. */
1881        SecondaryKeyCreator keyCreator = new PersistKeyCreator
1882            (catalog, entityMeta, keyCls.getName(), secKeyMeta);
1883
1884        /* Convert entity to bytes. */
1885        PersistEntityBinding entityBinding =
1886            new PersistEntityBinding(catalog, entityCls.getName(), false);
1887        entityBinding.objectToData(entity, dataEntry);
1888        entityBinding.objectToKey(entity, keyEntry);
1889
1890        /* Extract secondary key bytes from entity bytes. */
1891        DatabaseEntry secKeyEntry = new DatabaseEntry();
1892        boolean isKeyPresent = keyCreator.createSecondaryKey
1893            (null, keyEntry, dataEntry, secKeyEntry);
1894        assertEquals(keyValue != null, isKeyPresent);
1895
1896        /* Convert secondary key bytes back to an object. */
1897        PersistKeyBinding keyBinding =
1898            new PersistKeyBinding(catalog, keyCls.getName(), false);
1899        if (isKeyPresent) {
1900            Object keyValue2 = keyBinding.entryToObject(secKeyEntry);
1901            assertEquals(keyValue, keyValue2);
1902            DatabaseEntry secKeyEntry2 = new DatabaseEntry();
1903            keyBinding.objectToEntry(keyValue2, secKeyEntry2);
1904            assertEquals(secKeyEntry, secKeyEntry2);
1905        }
1906    }
1907
1908    private void checkSecMultiKey(MyEntity entity,
1909                                  String keyName,
1910                                  Set keyValues,
1911                                  Class keyCls)
1912        throws DatabaseException {
1913
1914        /* Get entity metadata. */
1915        Class entityCls = entity.getClass();
1916        EntityMetadata entityMeta =
1917            model.getEntityMetadata(entityCls.getName());
1918        assertNotNull(entityMeta);
1919
1920        /* Get secondary key metadata. */
1921        SecondaryKeyMetadata secKeyMeta =
1922            entityMeta.getSecondaryKeys().get(keyName);
1923        assertNotNull(secKeyMeta);
1924
1925        /* Create key creator/nullifier. */
1926        SecondaryMultiKeyCreator keyCreator = new PersistKeyCreator
1927            (catalog, entityMeta, keyCls.getName(), secKeyMeta);
1928
1929        /* Convert entity to bytes. */
1930        PersistEntityBinding entityBinding =
1931            new PersistEntityBinding(catalog, entityCls.getName(), false);
1932        entityBinding.objectToData(entity, dataEntry);
1933        entityBinding.objectToKey(entity, keyEntry);
1934
1935        /* Extract secondary key bytes from entity bytes. */
1936        Set<DatabaseEntry> results = new HashSet<DatabaseEntry>();
1937        keyCreator.createSecondaryKeys
1938            (null, keyEntry, dataEntry, results);
1939        assertEquals(keyValues.size(), results.size());
1940
1941        /* Convert secondary key bytes back to objects. */
1942        PersistKeyBinding keyBinding =
1943            new PersistKeyBinding(catalog, keyCls.getName(), false);
1944        Set keyValues2 = new HashSet();
1945        for (DatabaseEntry secKeyEntry : results) {
1946            Object keyValue2 = keyBinding.entryToObject(secKeyEntry);
1947            keyValues2.add(keyValue2);
1948        }
1949        assertEquals(keyValues, keyValues2);
1950    }
1951
1952    private void nullifySecKey(MyEntity entity,
1953                              String keyName,
1954                              Object keyValue,
1955                              Class keyCls)
1956        throws DatabaseException {
1957
1958        /* Get entity metadata. */
1959        Class entityCls = entity.getClass();
1960        EntityMetadata entityMeta =
1961            model.getEntityMetadata(entityCls.getName());
1962        assertNotNull(entityMeta);
1963
1964        /* Get secondary key metadata. */
1965        SecondaryKeyMetadata secKeyMeta =
1966            entityMeta.getSecondaryKeys().get(keyName);
1967        assertNotNull(secKeyMeta);
1968
1969        /* Create key creator/nullifier. */
1970        ForeignMultiKeyNullifier keyNullifier = new PersistKeyCreator
1971            (catalog, entityMeta, keyCls.getName(), secKeyMeta);
1972
1973        /* Convert entity to bytes. */
1974        PersistEntityBinding entityBinding =
1975            new PersistEntityBinding(catalog, entityCls.getName(), false);
1976        entityBinding.objectToData(entity, dataEntry);
1977        entityBinding.objectToKey(entity, keyEntry);
1978
1979        /* Convert secondary key to bytes. */
1980        PersistKeyBinding keyBinding =
1981            new PersistKeyBinding(catalog, keyCls.getName(), false);
1982        DatabaseEntry secKeyEntry = new DatabaseEntry();
1983        if (keyValue != null) {
1984            keyBinding.objectToEntry(keyValue, secKeyEntry);
1985        }
1986
1987        /* Nullify secondary key bytes within entity bytes. */
1988        boolean isKeyPresent = keyNullifier.nullifyForeignKey
1989            (null, keyEntry, dataEntry, secKeyEntry);
1990        assertEquals(keyValue != null, isKeyPresent);
1991
1992        /* Convert modified entity bytes back to an entity. */
1993        Object entity2 = entityBinding.entryToObject(keyEntry, dataEntry);
1994        setFieldToNull(entity, keyName);
1995        entity.validate(entity2);
1996
1997        /* Do a full check after nullifying it. */
1998        checkSecKey(entity, keyName, null, keyCls);
1999    }
2000
2001    private void nullifySecMultiKey(MyEntity entity,
2002                                    String keyName,
2003                                    Object keyValue,
2004                                    Class keyCls)
2005        throws DatabaseException {
2006
2007        /* Get entity metadata. */
2008        Class entityCls = entity.getClass();
2009        EntityMetadata entityMeta =
2010            model.getEntityMetadata(entityCls.getName());
2011        assertNotNull(entityMeta);
2012
2013        /* Get secondary key metadata. */
2014        SecondaryKeyMetadata secKeyMeta =
2015            entityMeta.getSecondaryKeys().get(keyName);
2016        assertNotNull(secKeyMeta);
2017
2018        /* Create key creator/nullifier. */
2019        ForeignMultiKeyNullifier keyNullifier = new PersistKeyCreator
2020            (catalog, entityMeta, keyCls.getName(), secKeyMeta);
2021
2022        /* Convert entity to bytes. */
2023        PersistEntityBinding entityBinding =
2024            new PersistEntityBinding(catalog, entityCls.getName(), false);
2025        entityBinding.objectToData(entity, dataEntry);
2026        entityBinding.objectToKey(entity, keyEntry);
2027
2028        /* Get secondary key binding. */
2029        PersistKeyBinding keyBinding =
2030            new PersistKeyBinding(catalog, keyCls.getName(), false);
2031        DatabaseEntry secKeyEntry = new DatabaseEntry();
2032
2033        /* Nullify one key value at a time until all of them are gone. */
2034        while (true) {
2035            Object fieldObj = getField(entity, keyName);
2036            fieldObj = nullifyFirstElement(fieldObj, keyBinding, secKeyEntry);
2037            if (fieldObj == null) {
2038                break;
2039            }
2040            setField(entity, keyName, fieldObj);
2041
2042            /* Nullify secondary key bytes within entity bytes. */
2043            boolean isKeyPresent = keyNullifier.nullifyForeignKey
2044                (null, keyEntry, dataEntry, secKeyEntry);
2045            assertEquals(keyValue != null, isKeyPresent);
2046
2047            /* Convert modified entity bytes back to an entity. */
2048            Object entity2 = entityBinding.entryToObject(keyEntry, dataEntry);
2049            entity.validate(entity2);
2050
2051            /* Do a full check after nullifying it. */
2052            Set keyValues;
2053            if (fieldObj instanceof Set) {
2054                keyValues = (Set) fieldObj;
2055            } else if (fieldObj instanceof Object[]) {
2056                keyValues = toSet((Object[]) fieldObj);
2057            } else if (fieldObj instanceof int[]) {
2058                keyValues = toSet((int[]) fieldObj);
2059            } else {
2060                throw new IllegalStateException(fieldObj.getClass().getName());
2061            }
2062            checkSecMultiKey(entity, keyName, keyValues, keyCls);
2063        }
2064    }
2065
2066    /**
2067     * Nullifies the first element of an array or collection object by removing
2068     * it from the array or collection.  Returns the resulting array or
2069     * collection.  Also outputs the removed element to the keyEntry using the
2070     * keyBinding.
2071     */
2072    private Object nullifyFirstElement(Object obj,
2073                                       EntryBinding keyBinding,
2074                                       DatabaseEntry keyEntry) {
2075        if (obj instanceof Collection) {
2076            Iterator i = ((Collection) obj).iterator();
2077            if (i.hasNext()) {
2078                Object elem = i.next();
2079                i.remove();
2080                keyBinding.objectToEntry(elem, keyEntry);
2081                return obj;
2082            } else {
2083                return null;
2084            }
2085        } else if (obj instanceof Object[]) {
2086            Object[] a1 = (Object[]) obj;
2087            if (a1.length > 0) {
2088                Object[] a2 = (Object[]) Array.newInstance
2089                    (obj.getClass().getComponentType(), a1.length - 1);
2090                System.arraycopy(a1, 1, a2, 0, a2.length);
2091                keyBinding.objectToEntry(a1[0], keyEntry);
2092                return a2;
2093            } else {
2094                return null;
2095            }
2096        } else if (obj instanceof int[]) {
2097            int[] a1 = (int[]) obj;
2098            if (a1.length > 0) {
2099                int[] a2 = new int[a1.length - 1];
2100                System.arraycopy(a1, 1, a2, 0, a2.length);
2101                keyBinding.objectToEntry(a1[0], keyEntry);
2102                return a2;
2103            } else {
2104                return null;
2105            }
2106        } else {
2107            throw new IllegalStateException(obj.getClass().getName());
2108        }
2109    }
2110
2111    private void checkMetadata(String clsName,
2112                               String[][] nameTypePairs,
2113                               int priKeyIndex,
2114                               String superClsName)
2115        throws DatabaseException {
2116
2117        /* Check metadata/types against the live model. */
2118        checkMetadata
2119            (catalog, model, clsName, nameTypePairs, priKeyIndex,
2120             superClsName);
2121
2122        /*
2123         * Open a catalog that uses the stored model.
2124         */
2125        PersistCatalog storedCatalog = new PersistCatalog
2126            (null, env, STORE_PREFIX, STORE_PREFIX + "catalog", null, null,
2127             null, false /*useCurrentModel*/, null /*Store*/);
2128        EntityModel storedModel = storedCatalog.getResolvedModel();
2129
2130        /* Check metadata/types against the stored catalog/model. */
2131        checkMetadata
2132            (storedCatalog, storedModel, clsName, nameTypePairs, priKeyIndex,
2133             superClsName);
2134
2135        storedCatalog.close();
2136    }
2137
2138    private void checkMetadata(PersistCatalog checkCatalog,
2139                               EntityModel checkModel,
2140                               String clsName,
2141                               String[][] nameTypePairs,
2142                               int priKeyIndex,
2143                               String superClsName)
2144        throws DatabaseException {
2145
2146        ClassMetadata classMeta = checkModel.getClassMetadata(clsName);
2147        assertNotNull(clsName, classMeta);
2148
2149        PrimaryKeyMetadata priKeyMeta = classMeta.getPrimaryKey();
2150        if (priKeyIndex >= 0) {
2151            assertNotNull(priKeyMeta);
2152            String fieldName = nameTypePairs[priKeyIndex][0];
2153            String fieldType = nameTypePairs[priKeyIndex][1];
2154            assertEquals(priKeyMeta.getName(), fieldName);
2155            assertEquals(priKeyMeta.getClassName(), fieldType);
2156            assertEquals(priKeyMeta.getDeclaringClassName(), clsName);
2157            assertNull(priKeyMeta.getSequenceName());
2158        } else {
2159            assertNull(priKeyMeta);
2160        }
2161
2162        RawType type = checkCatalog.getFormat(clsName);
2163        assertNotNull(type);
2164        assertEquals(clsName, type.getClassName());
2165        assertEquals(0, type.getVersion());
2166        assertTrue(!type.isSimple());
2167        assertTrue(!type.isPrimitive());
2168        assertTrue(!type.isEnum());
2169        assertNull(type.getEnumConstants());
2170        assertTrue(!type.isArray());
2171        assertEquals(0, type.getDimensions());
2172        assertNull(type.getComponentType());
2173        RawType superType = type.getSuperType();
2174        if (superClsName != null) {
2175            assertNotNull(superType);
2176            assertEquals(superClsName, superType.getClassName());
2177        } else {
2178            assertNull(superType);
2179        }
2180
2181        Map<String,RawField> fields = type.getFields();
2182        assertNotNull(fields);
2183
2184        int nFields = nameTypePairs.length;
2185        assertEquals(nFields, fields.size());
2186
2187        for (String[] pair : nameTypePairs) {
2188            String fieldName = pair[0];
2189            String fieldType = pair[1];
2190            Class fieldCls;
2191            try {
2192                fieldCls = SimpleCatalog.classForName(fieldType);
2193            } catch (ClassNotFoundException e) {
2194                fail(e.toString());
2195                return; /* For compiler */
2196            }
2197            RawField field = fields.get(fieldName);
2198            assertNotNull(field);
2199            assertEquals(fieldName, field.getName());
2200            type = field.getType();
2201            assertNotNull(type);
2202            int dim = getArrayDimensions(fieldType);
2203            while (dim > 0) {
2204                assertEquals(dim, type.getDimensions());
2205                assertEquals(dim, getArrayDimensions(fieldType));
2206                assertEquals(true, type.isArray());
2207                assertEquals(fieldType, type.getClassName());
2208                assertEquals(0, type.getVersion());
2209                assertTrue(!type.isSimple());
2210                assertTrue(!type.isPrimitive());
2211                assertTrue(!type.isEnum());
2212                assertNull(type.getEnumConstants());
2213                fieldType = getArrayComponent(fieldType, dim);
2214                type = type.getComponentType();
2215                assertNotNull(fieldType, type);
2216                dim -= 1;
2217            }
2218            assertEquals(fieldType, type.getClassName());
2219            List<String> enums = getEnumConstants(fieldType);
2220            assertEquals(isSimpleType(fieldType), type.isSimple());
2221            assertEquals(isPrimitiveType(fieldType), type.isPrimitive());
2222            assertNull(type.getComponentType());
2223            assertTrue(!type.isArray());
2224            assertEquals(0, type.getDimensions());
2225            if (enums != null) {
2226                assertTrue(type.isEnum());
2227                assertEquals(enums, type.getEnumConstants());
2228                assertNull(type.getSuperType());
2229            } else {
2230                assertTrue(!type.isEnum());
2231                assertNull(type.getEnumConstants());
2232            }
2233        }
2234    }
2235
2236    private List<String> getEnumConstants(String clsName) {
2237        if (isPrimitiveType(clsName)) {
2238            return null;
2239        }
2240        Class cls;
2241        try {
2242            cls = Class.forName(clsName);
2243        } catch (ClassNotFoundException e) {
2244            fail(e.toString());
2245            return null; /* Never happens. */
2246        }
2247        if (!cls.isEnum()) {
2248            return null;
2249        }
2250        List<String> enums = new ArrayList<String>();
2251        Object[] vals = cls.getEnumConstants();
2252        for (Object val : vals) {
2253            enums.add(val.toString());
2254        }
2255        return enums;
2256    }
2257
2258    private String getArrayComponent(String clsName, int dim) {
2259        clsName = clsName.substring(1);
2260        if (dim > 1) {
2261            return clsName;
2262        }
2263        if (clsName.charAt(0) == 'L' &&
2264            clsName.charAt(clsName.length() - 1) == ';') {
2265            return clsName.substring(1, clsName.length() - 1);
2266        }
2267        if (clsName.length() != 1) {
2268            fail();
2269        }
2270        switch (clsName.charAt(0)) {
2271        case 'Z': return "boolean";
2272        case 'B': return "byte";
2273        case 'C': return "char";
2274        case 'D': return "double";
2275        case 'F': return "float";
2276        case 'I': return "int";
2277        case 'J': return "long";
2278        case 'S': return "short";
2279        default: fail();
2280        }
2281        return null; /* Should never happen. */
2282    }
2283
2284    private static int getArrayDimensions(String clsName) {
2285        int i = 0;
2286        while (clsName.charAt(i) == '[') {
2287            i += 1;
2288        }
2289        return i;
2290    }
2291
2292    private static boolean isSimpleType(String clsName) {
2293        return isPrimitiveType(clsName) ||
2294               clsName.equals("java.lang.Boolean") ||
2295               clsName.equals("java.lang.Character") ||
2296               clsName.equals("java.lang.Byte") ||
2297               clsName.equals("java.lang.Short") ||
2298               clsName.equals("java.lang.Integer") ||
2299               clsName.equals("java.lang.Long") ||
2300               clsName.equals("java.lang.Float") ||
2301               clsName.equals("java.lang.Double") ||
2302               clsName.equals("java.lang.String") ||
2303               clsName.equals("java.math.BigInteger") ||
2304               //clsName.equals("java.math.BigDecimal") ||
2305               clsName.equals("java.util.Date");
2306    }
2307
2308    private static boolean isPrimitiveType(String clsName) {
2309        return clsName.equals("boolean") ||
2310               clsName.equals("char") ||
2311               clsName.equals("byte") ||
2312               clsName.equals("short") ||
2313               clsName.equals("int") ||
2314               clsName.equals("long") ||
2315               clsName.equals("float") ||
2316               clsName.equals("double");
2317    }
2318
2319    interface MyEntity {
2320        Object getPriKeyObject();
2321        void validate(Object other);
2322    }
2323
2324    private static boolean nullOrEqual(Object o1, Object o2) {
2325        return (o1 != null) ? o1.equals(o2) : (o2 == null);
2326    }
2327
2328    private static String arrayToString(Object[] array) {
2329        StringBuffer buf = new StringBuffer();
2330        buf.append('[');
2331        for (Object o : array) {
2332            if (o instanceof Object[]) {
2333                buf.append(arrayToString((Object[]) o));
2334            } else {
2335                buf.append(o);
2336            }
2337            buf.append(',');
2338        }
2339        buf.append(']');
2340        return buf.toString();
2341    }
2342
2343    private void setFieldToNull(Object obj, String fieldName) {
2344        try {
2345            Field field = obj.getClass().getDeclaredField(fieldName);
2346            field.setAccessible(true);
2347            field.set(obj, null);
2348        } catch (NoSuchFieldException e) {
2349            fail(e.toString());
2350        } catch (IllegalAccessException e) {
2351            fail(e.toString());
2352        }
2353    }
2354
2355    private void setField(Object obj, String fieldName, Object fieldValue) {
2356        try {
2357            Field field = obj.getClass().getDeclaredField(fieldName);
2358            field.setAccessible(true);
2359            field.set(obj, fieldValue);
2360        } catch (NoSuchFieldException e) {
2361            throw new IllegalStateException(e.toString());
2362        } catch (IllegalAccessException e) {
2363            throw new IllegalStateException(e.toString());
2364        }
2365    }
2366
2367    private Object getField(Object obj, String fieldName) {
2368        try {
2369            Field field = obj.getClass().getDeclaredField(fieldName);
2370            field.setAccessible(true);
2371            return field.get(obj);
2372        } catch (NoSuchFieldException e) {
2373            throw new IllegalStateException(e.toString());
2374        } catch (IllegalAccessException e) {
2375            throw new IllegalStateException(e.toString());
2376        }
2377    }
2378}
2379