1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2002,2008 Oracle.  All rights reserved.
5 *
6 * $Id: CompositeKeyFormat.java,v 1.1 2008/02/07 17:12:27 mark Exp $
7 */
8
9package com.sleepycat.persist.impl;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.HashMap;
14import java.util.IdentityHashMap;
15import java.util.List;
16import java.util.Map;
17
18import com.sleepycat.persist.model.ClassMetadata;
19import com.sleepycat.persist.model.FieldMetadata;
20import com.sleepycat.persist.raw.RawField;
21import com.sleepycat.persist.raw.RawObject;
22
23/**
24 * Format for a composite key class.
25 *
26 * This class is similar to ComplexFormat in that a composite key class and
27 * other complex classes have fields, and the Accessor interface is used to
28 * access those fields.  Composite key classes are different in the following
29 * ways:
30 *
31 * - The superclass must be Object.  No inheritance is allowed.
32 *
33 * - All instance fields must be annotated with @KeyField, which determines
34 *   their order in the data bytes.
35 *
36 * - Although fields may be reference types (primitive wrappers or other simple
37 *   reference types), they are stored as if they were primitives.  No object
38 *   format ID is stored, and the class of the object must be the declared
39 *   classs of the field; i.e., no polymorphism is allowed for key fields.
40 *   In other words, a composite key is stored as an ordinary tuple as defined
41 *   in the com.sleepycat.bind.tuple package.  This keeps the key small and
42 *   gives it a well defined sort order.
43 *
44 * - If the key class implements Comparable, it is called by the Database
45 *   btree comparator.  It must therefore be available during JE recovery,
46 *   before the store and catalog have been opened.  To support this, this
47 *   format can be constructed during recovery.  A SimpleCatalog singleton
48 *   instance is used to provide a catalog of simple types that is used by
49 *   the composite key format.
50 *
51 * - When interacting with the Accessor, the composite key format treats the
52 *   Accessor's non-key fields as its key fields.  The Accessor's key fields
53 *   are secondary keys, while the composite format's key fields are the
54 *   component parts of a single key.
55 *
56 * @author Mark Hayes
57 */
58public class CompositeKeyFormat extends Format {
59
60    private static final long serialVersionUID = 306843428409314630L;
61
62    private ClassMetadata metadata;
63    private List<FieldInfo> fields;
64    private transient Accessor objAccessor;
65    private transient Accessor rawAccessor;
66    private transient volatile Map<String,RawField> rawFields;
67    private transient volatile FieldInfo[] rawInputFields;
68
69    static String[] getFieldNameArray(List<FieldMetadata> list) {
70        int index = 0;
71        String[] a = new String[list.size()];
72        for (FieldMetadata f : list) {
73            a[index] = f.getName();
74            index += 1;
75        }
76        return a;
77    }
78
79    CompositeKeyFormat(Class cls,
80                       ClassMetadata metadata,
81                       List<FieldMetadata> fieldNames) {
82        this(cls, metadata, getFieldNameArray(fieldNames));
83    }
84
85    CompositeKeyFormat(Class cls,
86                       ClassMetadata metadata,
87                       String[] fieldNames) {
88        super(cls);
89        this.metadata = metadata;
90
91        /* Check that the superclass is Object. */
92        Class superCls = cls.getSuperclass();
93        if (superCls != Object.class) {
94            throw new IllegalArgumentException
95                ("Composite key class must be derived from Object: " +
96                 cls.getName());
97        }
98
99        /* Populate fields list in fieldNames order. */
100        List<FieldInfo> instanceFields = FieldInfo.getInstanceFields(cls);
101        fields = new ArrayList<FieldInfo>(instanceFields.size());
102        for (String fieldName : fieldNames) {
103            FieldInfo field = null;
104            for (FieldInfo tryField : instanceFields) {
105                if (fieldName.equals(tryField.getName())) {
106                    field = tryField;
107                    break;
108                }
109            }
110            if (field == null) {
111                throw new IllegalArgumentException
112                    ("Composite key field is not an instance field:" +
113                     getClassName() + '.' + fieldName);
114            }
115            fields.add(field);
116            instanceFields.remove(field);
117            if (!SimpleCatalog.isSimpleType(field.getFieldClass())) {
118                throw new IllegalArgumentException
119                    ("Composite key field is not a simple type: " +
120                     getClassName() + '.' + fieldName);
121            }
122        }
123        if (instanceFields.size() > 0) {
124            throw new IllegalArgumentException
125                ("All composite key instance fields must be key fields: " +
126                 getClassName() + '.' + instanceFields.get(0).getName());
127        }
128    }
129
130    @Override
131    void migrateFromBeta(Map<String,Format> formatMap) {
132        super.migrateFromBeta(formatMap);
133        for (FieldInfo field : fields) {
134            field.migrateFromBeta(formatMap);
135        }
136    }
137
138    @Override
139    boolean isModelClass() {
140        return true;
141    }
142
143    @Override
144    ClassMetadata getClassMetadata() {
145        if (metadata == null) {
146            throw new IllegalStateException(getClassName());
147        }
148        return metadata;
149    }
150
151    @Override
152    public Map<String,RawField> getFields() {
153
154        /*
155         * Lazily create the raw type information.  Synchronization is not
156         * required since this object is immutable.  If by chance we create two
157         * maps when two threads execute this block, no harm is done.  But be
158         * sure to assign the rawFields field only after the map is fully
159         * populated.
160         */
161        if (rawFields == null) {
162            Map<String,RawField> map = new HashMap<String,RawField>();
163            for (RawField field : fields) {
164                map.put(field.getName(), field);
165            }
166            rawFields = map;
167        }
168        return rawFields;
169    }
170
171    @Override
172    void collectRelatedFormats(Catalog catalog,
173                               Map<String,Format> newFormats) {
174        /* Collect field formats. */
175        for (FieldInfo field : fields) {
176            field.collectRelatedFormats(catalog, newFormats);
177        }
178    }
179
180    @Override
181    void initialize(Catalog catalog, int initVersion) {
182        /* Initialize all fields. */
183        for (FieldInfo field : fields) {
184            field.initialize(catalog, initVersion);
185        }
186        /* Create the accessor. */
187        Class type = getType();
188        if (type != null) {
189            if (EnhancedAccessor.isEnhanced(type)) {
190                objAccessor = new EnhancedAccessor(type);
191            } else {
192                objAccessor = new ReflectionAccessor(catalog, type, fields);
193            }
194        }
195        rawAccessor = new RawAccessor(this, fields);
196    }
197
198    @Override
199    Object newArray(int len) {
200        return objAccessor.newArray(len);
201    }
202
203    @Override
204    public Object newInstance(EntityInput input, boolean rawAccess) {
205        Accessor accessor = rawAccess ? rawAccessor : objAccessor;
206        return accessor.newInstance();
207    }
208
209    @Override
210    public Object readObject(Object o, EntityInput input, boolean rawAccess) {
211        Accessor accessor = rawAccess ? rawAccessor : objAccessor;
212        accessor.readNonKeyFields(o, input, 0, Accessor.MAX_FIELD_NUM, -1);
213        return o;
214    }
215
216    @Override
217    void writeObject(Object o, EntityOutput output, boolean rawAccess) {
218        Accessor accessor = rawAccess ? rawAccessor : objAccessor;
219        accessor.writeNonKeyFields(o, output);
220    }
221
222    @Override
223    Object convertRawObject(Catalog catalog,
224                            boolean rawAccess,
225                            RawObject rawObject,
226                            IdentityHashMap converted) {
227
228        /*
229         * Synchronization is not required since rawInputFields is immutable.
230         * If by chance we create duplicate values when two threads execute
231         * this block, no harm is done.  But be sure to assign the field only
232         * after the values are fully populated.
233         */
234        FieldInfo[] myFields = rawInputFields;
235        if (myFields == null) {
236            myFields = new FieldInfo[fields.size()];
237            fields.toArray(myFields);
238            rawInputFields = myFields;
239        }
240        if (rawObject.getSuper() != null) {
241            throw new IllegalArgumentException
242                ("RawObject has too many superclasses: " +
243                 rawObject.getType().getClassName());
244        }
245        RawObject[] objects = new RawObject[myFields.length];
246        Arrays.fill(objects, rawObject);
247        EntityInput in = new RawComplexInput
248            (catalog, rawAccess, converted, myFields, objects);
249        Object o = newInstance(in, rawAccess);
250        converted.put(rawObject, o);
251        return readObject(o, in, rawAccess);
252    }
253
254    @Override
255    void skipContents(RecordInput input) {
256        int maxNum = fields.size();
257        for (int i = 0; i < maxNum; i += 1) {
258            fields.get(i).getType().skipContents(input);
259        }
260    }
261
262    @Override
263    void copySecKey(RecordInput input, RecordOutput output) {
264        int maxNum = fields.size();
265        for (int i = 0; i < maxNum; i += 1) {
266            fields.get(i).getType().copySecKey(input, output);
267        }
268    }
269
270    @Override
271    Format getSequenceKeyFormat() {
272        if (fields.size() != 1) {
273            throw new IllegalArgumentException
274                ("A composite key class used with a sequence may contain " +
275                 "only a single integer key field: " + getClassName());
276        }
277        return fields.get(0).getType().getSequenceKeyFormat();
278    }
279
280    @Override
281    boolean evolve(Format newFormatParam, Evolver evolver) {
282
283        /* Disallow evolution to a non-composite format. */
284        if (!(newFormatParam instanceof CompositeKeyFormat)) {
285            evolver.addEvolveError
286                (this, newFormatParam, null,
287                 "A composite key class may not be changed to a different " +
288                 "type");
289            return false;
290        }
291        CompositeKeyFormat newFormat = (CompositeKeyFormat) newFormatParam;
292
293        /* Check for added or removed key fields. */
294        if (fields.size() != newFormat.fields.size()) {
295            evolver.addEvolveError
296                (this, newFormat,
297                 "Composite key class fields were added or removed ",
298                 "Old fields: " + fields +
299                 " new fields: " + newFormat.fields);
300            return false;
301        }
302
303        /* Check for modified key fields. */
304        boolean newVersion = false;
305        for (int i = 0; i < fields.size(); i += 1) {
306            int result = evolver.evolveRequiredKeyField
307                (this, newFormat, fields.get(i),
308                 newFormat.fields.get(i));
309            if (result == Evolver.EVOLVE_FAILURE) {
310                return false;
311            }
312            if (result == Evolver.EVOLVE_NEEDED) {
313                newVersion = true;
314            }
315        }
316
317        /*
318         * We never need to use a custom reader because the physical key field
319         * formats never change.  But we do create a new evolved format when
320         * a type changes (primitive <-> primitive wrapper) so that the new
321         * type information is correct.
322         */
323        if (newVersion) {
324            evolver.useEvolvedFormat(this, newFormat, newFormat);
325        } else {
326            evolver.useOldFormat(this, newFormat);
327        }
328        return true;
329    }
330}
331